From e36996c209ee1228dff9304d8b3ef95af48cf22f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 13:14:52 -0500 Subject: [PATCH 001/236] update First pass merge of distributed authority feature. --- LICENSE.md | 10 +- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Messages/NetworkTransformMessage.cs | 146 +- .../Components/NetworkAnimator.cs | 179 ++- .../Components/NetworkRigidbody.cs | 112 +- .../Components/NetworkRigidbody2D.cs | 11 +- .../Components/NetworkTransform.cs | 717 ++++++--- .../RigidbodyContactEventManager.cs | 230 +++ .../RigidbodyContactEventManager.cs.meta | 11 + .../Editor/CodeGen/NetworkBehaviourILPP.cs | 4 + .../Configuration/NetcodeSettingsProvider.cs | 2 +- .../Editor/NetworkManagerEditor.cs | 508 ++---- .../Editor/NetworkObjectEditor.cs | 53 + com.unity.netcode.gameobjects/LICENSE.md | 10 +- .../Runtime/Configuration/NetworkConfig.cs | 17 +- .../Runtime/Connection/NetworkClient.cs | 72 +- .../Connection/NetworkConnectionManager.cs | 362 ++++- .../Runtime/Core/NetworkBehaviour.cs | 525 ++++++- .../Runtime/Core/NetworkBehaviourUpdater.cs | 13 +- .../Runtime/Core/NetworkManager.cs | 204 ++- .../Runtime/Core/NetworkObject.cs | 1390 ++++++++++++++++- .../Runtime/Logging/NetworkLog.cs | 37 +- .../Runtime/Messaging/CustomMessageManager.cs | 16 +- .../Messaging/DeferredMessageManager.cs | 41 +- .../IDeferredNetworkMessageManager.cs | 5 + .../Messages/ChangeOwnershipMessage.cs | 380 ++++- .../Messages/ClientConnectedMessage.cs | 43 +- .../Messages/ClientDisconnectedMessage.cs | 4 + .../Messages/ConnectionApprovedMessage.cs | 93 +- .../Messages/ConnectionRequestMessage.cs | 21 + .../Messaging/Messages/CreateObjectMessage.cs | 290 +++- .../Messages/DestroyObjectMessage.cs | 153 +- .../Messages/NetworkVariableDeltaMessage.cs | 87 +- .../Messaging/Messages/ParentSyncMessage.cs | 52 + .../Messaging/Messages/ProxyMessage.cs | 19 +- .../Runtime/Messaging/Messages/RpcMessages.cs | 157 +- .../Messaging/Messages/SceneEventMessage.cs | 5 +- .../Messaging/Messages/ServerLogMessage.cs | 28 +- .../Messaging/Messages/SessionOwnerMessage.cs | 29 + .../Messaging/NetworkMessageManager.cs | 32 + .../RpcTargets/AuthorityRpcTarget.cs | 77 + .../RpcTargets/AuthorityRpcTarget.cs.meta | 11 + .../RpcTargets/LocalSendRpcTarget.cs | 6 +- .../RpcTargets/NotAuthorityRpcTarget.cs | 67 + .../RpcTargets/NotAuthorityRpcTarget.cs.meta | 11 + .../RpcTargets/NotServerRpcTarget.cs | 4 +- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 41 +- .../Messaging/RpcTargets/ServerRpcTarget.cs | 10 +- .../CollectionSerializationUtility.cs | 29 +- .../Collections/NetworkList.cs | 47 +- .../NetworkVariable/NetworkVariable.cs | 29 +- .../NetworkVariable/NetworkVariableBase.cs | 99 +- .../NetworkVariableSerialization.cs | 39 + .../NetworkVariable/ResizableBitVector.cs | 8 + .../DefaultSceneManagerHandler.cs | 11 +- .../SceneManagement/ISceneManagerHandler.cs | 3 + .../SceneManagement/NetworkSceneManager.cs | 973 +++++++++++- .../Runtime/SceneManagement/SceneEventData.cs | 328 +++- .../SceneManagement/SceneEventProgress.cs | 22 +- .../Runtime/Serialization/FastBufferReader.cs | 5 + .../Runtime/Serialization/FastBufferWriter.cs | 24 + .../NetworkBehaviourReference.cs | 4 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 1002 +++++++++++- .../Runtime/Timing/IRealTimeProvider.cs | 3 + .../Runtime/Timing/NetworkTime.cs | 4 +- .../Runtime/Timing/RealTimeProvider.cs | 3 + .../Transports/UTP/BatchedSendQueue.cs | 20 + .../Runtime/IntegrationTestSceneHandler.cs | 4 + .../IntegrationTestWithApproximation.cs | 31 +- .../TestHelpers/Runtime/MockTimeProvider.cs | 5 + .../Runtime/NetcodeIntegrationTest.cs | 365 ++++- .../Runtime/NetcodeIntegrationTestHelpers.cs | 55 +- .../Serialization/FastBufferWriterTests.cs | 5 +- .../Transports/BatchedReceiveQueueTests.cs | 2 + .../Transports/BatchedSendQueueTests.cs | 2 + .../Runtime/ConnectionApprovalTimeoutTests.cs | 13 +- .../Tests/Runtime/DeferredMessagingTests.cs | 36 +- .../Tests/Runtime/DistributedAuthority.meta | 8 + .../DeferredDespawningTests.cs | 268 ++++ .../DeferredDespawningTests.cs.meta | 11 + .../DistributeObjectsTests.cs | 522 +++++++ .../DistributeObjectsTests.cs.meta | 11 + .../DistributedAuthorityCodecTests.cs | 720 +++++++++ .../DistributedAuthorityCodecTests.cs.meta | 11 + .../NetworkClientAndPlayerObjectTests.cs | 300 ++++ .../NetworkClientAndPlayerObjectTests.cs.meta | 11 + .../OwnershipPermissionsTests.cs | 408 +++++ .../OwnershipPermissionsTests.cs.meta | 11 + .../Tests/Runtime/HiddenVariableTests.cs | 24 +- .../Tests/Runtime/ListChangedTest.cs | 9 + .../Runtime/NetworkBehaviourUpdaterTests.cs | 248 ++- .../NetworkObjectDestroyTests.cs | 102 +- .../NetworkObjectDontDestroyWithOwnerTests.cs | 21 + .../NetworkObjectOnNetworkDespawnTests.cs | 109 +- .../NetworkObjectOnSpawnTests.cs | 50 +- .../NetworkObjectOwnershipPropertiesTests.cs | 233 +++ .../NetworkObjectOwnershipTests.cs | 89 +- .../NetworkObjectSpawnManyObjectsTests.cs | 8 + .../NetworkObjectSynchronizationTests.cs | 216 ++- .../Tests/Runtime/NetworkShowHideTests.cs | 189 ++- .../Tests/Runtime/NetworkSpawnManagerTests.cs | 62 +- .../NetworkTransform/NetworkTransformBase.cs | 12 +- .../NetworkTransformOwnershipTests.cs | 148 +- .../NetworkTransformPacketLossTests.cs | 22 +- .../NetworkTransformStateTests.cs | 2 + .../NetworkTransform/NetworkTransformTests.cs | 14 +- .../Tests/Runtime/NetworkVarBufferCopyTest.cs | 65 +- .../Tests/Runtime/NetworkVariableTests.cs | 94 +- .../Tests/Runtime/NetworkVisibilityTests.cs | 17 +- .../Tests/Runtime/OwnerModifiedTests.cs | 18 +- .../Tests/Runtime/OwnerPermissionTests.cs | 8 +- .../Runtime/PeerDisconnectCallbackTests.cs | 7 +- .../Runtime/Physics/NetworkRigidbodyTest.cs | 103 +- .../Tests/Runtime/PlayerObjectTests.cs | 12 + .../Tests/Runtime/RpcTests.cs | 11 +- .../Runtime/RpcTypeSerializationTests.cs | 24 +- .../Runtime/Timing/TimeInitializationTest.cs | 4 +- .../Runtime/TransformInterpolationTests.cs | 2 +- .../Tests/Runtime/UniversalRpcTests.cs | 273 ++++ .../ValidationExceptions.json | 10 +- com.unity.netcode.gameobjects/package.json | 8 +- .../InSceneParentChildHandler.cs | 291 +++- .../ParentingAutoSyncManager.cs | 64 + .../ParentingAutoSyncManager.cs.meta | 11 + .../ReparentingCubeNetBhv.cs | 13 + .../ReparentingCubeNetBhv.cs.meta | 11 + .../AnimatedCubeController.cs | 9 - .../PrefabTestAssets/InSceneDefined.prefab | 84 + .../InSceneDefined.prefab.meta | 7 + .../PrefabListTestAsset.asset | 8 + .../PrefabTestAssets/PrefabTestScene.unity | 160 +- .../Scripts/IntegrationNetworkTransform.cs | 10 +- .../ParentPlayerToInSceneNetworkObject.cs | 40 +- .../Manual/Scripts/PlayerMovementManager.cs | 2 +- .../Tests/Manual/Scripts/RandomMovement.cs | 8 +- .../Assets/Tests/Runtime/AddressablesTests.cs | 5 +- .../Runtime/Animation/NetworkAnimatorTests.cs | 156 +- .../Animation/Resources/AnimatorObject.prefab | 57 +- .../Resources/OwnerAnimatorObject.prefab | 57 +- .../ClientSynchronizationValidationTest.cs | 7 + .../InScenePlacedNetworkObjectTests.cs | 7 + .../NetworkObjectTestComponent.cs | 11 +- .../NetworkSceneManagerEventCallbacks.cs | 3 + .../NetworkSceneManagerEventDataPoolTest.cs | 4 + .../NetworkSceneManagerEventNotifications.cs | 5 +- .../NetworkSceneManagerFixValidationTests.cs | 5 +- ...NetworkSceneManagerPopulateInSceneTests.cs | 8 + .../NetworkSceneManagerSeneVerification.cs | 4 + .../NetworkSceneManagerUsageTests.cs | 17 +- .../SceneEventProgressTests.cs | 8 + .../NestedNetworkTransformTests.cs | 5 - .../Assets/Tests/Runtime/ObjectParenting.meta | 2 +- .../ObjectParenting/BlueColMat.mat.meta | 2 +- .../ObjectParenting/GrayColMat.mat.meta | 2 +- .../ObjectParenting/GreenColMat.mat.meta | 2 +- .../NetworkObjectParentingTests.cs | 22 + .../NetworkObjectParentingTests.cs.meta | 2 +- .../NetworkObjectParentingTests.unity | 334 +++- .../NetworkObjectParentingTests.unity.meta | 2 +- .../ParentDynamicUnderInScenePlaced.cs | 59 + .../ParentDynamicUnderInScenePlaced.cs.meta | 2 +- .../ParentingInSceneObjects.unity | 275 +++- .../ParentingInSceneObjects.unity.meta | 2 +- .../ParentingInSceneObjectsTests.cs | 151 +- .../ParentingInSceneObjectsTests.cs.meta | 2 +- .../ParentingWorldPositionStaysTests.cs | 8 + .../ParentingWorldPositionStaysTests.cs.meta | 2 +- .../ObjectParenting/PlayerPrefab.prefab.meta | 2 +- .../Tests/Runtime/PrefabExtendedTests.cs | 24 +- .../RespawnInSceneObjectsAfterShutdown.cs | 8 + .../Assets/Tests/Runtime/RpcObserverTests.cs | 3 + .../Runtime/RpcUserSerializableTypesTest.cs | 697 +++++++-- .../Assets/Tests/Runtime/SenderIdTests.cs | 8 + .../Runtime/ServerDisconnectsClientTest.cs | 9 + testproject/Packages/manifest.json | 16 +- testproject/Packages/packages-lock.json | 112 +- .../ProjectSettings/EditorBuildSettings.asset | 12 +- .../ProjectSettings/ProjectVersion.txt | 4 +- 178 files changed, 14705 insertions(+), 2098 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs create mode 100644 com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs.meta create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs create mode 100644 testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs.meta create mode 100644 testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab create mode 100644 testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab.meta diff --git a/LICENSE.md b/LICENSE.md index a5eb171398..031978c204 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,9 +1,7 @@ -MIT License +Unity Companion License (UCL License) -Copyright (c) 2021 Unity Technologies +com.unity.netcode.gameobjects copyright © 2021-2024 Unity Technologies +Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license). +Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c45db7c11e..8052e25506 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -43,6 +43,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) - Fixed issue where a client disconnected by a server-host would not receive a local notification. (#2789) - Fixed issue where a server-host could shutdown during a relay connection but periodically the transport disconnect message sent to any connected clients could be dropped. (#2789) - Fixed issue where a host could disconnect its local client but remain running as a server. (#2789) diff --git a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs index 1cdd9fa613..0ff0a42959 100644 --- a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs @@ -11,6 +11,21 @@ internal struct NetworkTransformMessage : INetworkMessage public int Version => 0; public ulong NetworkObjectId; public int NetworkBehaviourId; +#if NGO_DAMODE + // This is only used when serializing but not serialized + public bool DistributedAuthorityMode; + // Might get removed + public ulong[] TargetIds; + + private int GetTargetIdLength() + { + if (TargetIds != null) + { + return TargetIds.Length; + } + return 0; + } +#endif public NetworkTransform.NetworkTransformState State; private NetworkTransform m_ReceiverNetworkTransform; @@ -32,6 +47,22 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, NetworkBehaviourId); writer.WriteNetworkSerializable(State); +#if NGO_DAMODE + if (DistributedAuthorityMode) + { + var length = GetTargetIdLength(); + BytePacker.WriteValuePacked(writer, length); + // If no target ids, then just exit early (DAHost specific) + if (length == 0) + { + return; + } + foreach (var target in TargetIds) + { + BytePacker.WriteValuePacked(writer, target); + } + } +#endif } } @@ -45,39 +76,101 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } var currentPosition = reader.Position; ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) + var isSpawnedLocally = networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId); +#if NGO_DAMODE + // Only defer if the NetworkObject is not spawned yet and the local NetworkManager is not running as a DAHost. + if (!isSpawnedLocally && !networkManager.DAHost) +#else + if (!isSpawnedLocally) +#endif { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); return false; } + + // While the below check and assignment might seem out of place, this is specific to running in DAHost mode when a NetworkObject is + // hidden from the DAHost but is visible to other clients. Since the DAHost needs to forward updates to the clients, we ignore processing + // this message locally + var networkObject = (NetworkObject)null; + var isServerAuthoritative = false; + var ownerAuthoritativeServerSide = false; + // Get the behaviour index ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourId); // Deserialize the state - reader.ReadNetworkSerializable(out State); + reader.ReadNetworkSerializableInPlace(ref State); - var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + var targetCount = 0; + ByteUnpacker.ReadValueBitPacked(reader, out targetCount); + if (targetCount > 0) + { + TargetIds = new ulong[targetCount]; + } + var targetId = (ulong)0; + for (int i = 0; i < targetCount; i++) + { + ByteUnpacker.ReadValueBitPacked(reader, out targetId); + TargetIds[i] = targetId; + } + } +#endif - // Get the target NetworkTransform - m_ReceiverNetworkTransform = networkObject.ChildNetworkBehaviours[NetworkBehaviourId] as NetworkTransform; + if (isSpawnedLocally) + { + networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; + // Get the target NetworkTransform + m_ReceiverNetworkTransform = networkObject.ChildNetworkBehaviours[NetworkBehaviourId] as NetworkTransform; + isServerAuthoritative = m_ReceiverNetworkTransform.IsServerAuthoritative(); + ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer; + } +#if NGO_DAMODE + else + { + // If we are the DAHost and the NetworkObject is hidden from the host we still need to forward this message + ownerAuthoritativeServerSide = networkManager.DAHost && !isSpawnedLocally; + } +#endif - var isServerAuthoritative = m_ReceiverNetworkTransform.IsServerAuthoritative(); - var ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer; if (ownerAuthoritativeServerSide) { - var ownerClientId = networkObject.OwnerClientId; - if (ownerClientId == NetworkManager.ServerClientId) + var ownerClientId = (ulong)0; + + if (networkObject != null) { - // Ownership must have changed, ignore any additional pending messages that might have - // come from a previous owner client. - return true; + ownerClientId = networkObject.OwnerClientId; + if (ownerClientId == NetworkManager.ServerClientId) + { + // Ownership must have changed, ignore any additional pending messages that might have + // come from a previous owner client. + return true; + } } +#if NGO_DAMODE + else if (networkManager.DAHost) + { + // Specific to distributed authority mode, the only sender of state updates will be the owner + ownerClientId = context.SenderId; + } +#endif var networkDelivery = State.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; // Forward the state update if there are any remote clients to foward it to if (networkManager.ConnectionManager.ConnectedClientsList.Count > (networkManager.IsHost ? 2 : 1)) { +#if NGO_DAMODE + var clientCount = networkManager.DistributedAuthorityMode ? GetTargetIdLength() : networkManager.ConnectionManager.ConnectedClientsList.Count; + if (clientCount == 0) + { + return true; + } +#else + var clientCount = networkManager.ConnectionManager.ConnectedClientsList.Count; +#endif // This is only to copy the existing and already serialized struct for forwarding purposes only. // This will not include any changes made to this struct at this particular stage of processing the message. var currentMessage = this; @@ -86,15 +179,29 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int // Rewind the new reader to the beginning of the message's payload currentMessage.m_CurrentReader.Seek(currentPosition); // Forward the message to all connected clients that are observers of the associated NetworkObject - var clientCount = networkManager.ConnectionManager.ConnectedClientsList.Count; + for (int i = 0; i < clientCount; i++) { +#if NGO_DAMODE + var clientId = networkManager.DistributedAuthorityMode ? TargetIds[i] : networkManager.ConnectionManager.ConnectedClientsList[i].ClientId; + if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) || + (!networkManager.DistributedAuthorityMode && !networkObject.Observers.Contains(clientId))) +#else var clientId = networkManager.ConnectionManager.ConnectedClientsList[i].ClientId; if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) || !networkObject.Observers.Contains(clientId)) +#endif { continue; } - networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); + + if (clientId > 1) + { + networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); + } + else + { + networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); + } } // Dispose of the reader used for forwarding currentMessage.m_CurrentReader.Dispose(); @@ -105,6 +212,17 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { +#if NGO_DAMODE + var networkManager = context.SystemOwner as NetworkManager; + // Only if the local NetworkManager instance is running as the DAHost we just exit if there is no local + // NetworkTransform component to apply the state update to (i.e. it is hidden from the DAHost and it + // just forwarded the state update to any other connected client) + if (networkManager.DAHost && m_ReceiverNetworkTransform == null) + { + return; + } +#endif + if (m_ReceiverNetworkTransform == null) { Debug.LogError($"[{nameof(NetworkTransformMessage)}][Dropped] Reciever {nameof(NetworkTransform)} was not set!"); diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index 79030b7be7..2f5cea5cfe 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -25,26 +25,53 @@ private void FlushMessages() { foreach (var animationUpdate in m_SendAnimationUpdates) { - m_NetworkAnimator.SendAnimStateClientRpc(animationUpdate.AnimationMessage, animationUpdate.ClientRpcParams); +#if NGO_DAMODE + if (m_NetworkAnimator.NetworkManager.DistributedAuthorityMode) + { + m_NetworkAnimator.SendAnimStateRpc(animationUpdate.AnimationMessage); + } + else +#endif + { + m_NetworkAnimator.SendAnimStateClientRpc(animationUpdate.AnimationMessage, animationUpdate.ClientRpcParams); + } } m_SendAnimationUpdates.Clear(); foreach (var sendEntry in m_SendParameterUpdates) { - m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams); +#if NGO_DAMODE + if (m_NetworkAnimator.NetworkManager.DistributedAuthorityMode) + { + m_NetworkAnimator.SendParametersUpdateRpc(sendEntry.ParametersUpdateMessage); + } + else +#endif + { + m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams); + } } m_SendParameterUpdates.Clear(); foreach (var sendEntry in m_SendTriggerUpdates) { - if (!sendEntry.SendToServer) +#if NGO_DAMODE + if (m_NetworkAnimator.NetworkManager.DistributedAuthorityMode) { - m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams); + m_NetworkAnimator.SendAnimTriggerRpc(sendEntry.AnimationTriggerMessage); } else +#endif { - m_NetworkAnimator.SendAnimTriggerServerRpc(sendEntry.AnimationTriggerMessage); + if (!sendEntry.SendToServer) + { + m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams); + } + else + { + m_NetworkAnimator.SendAnimTriggerServerRpc(sendEntry.AnimationTriggerMessage); + } } } m_SendTriggerUpdates.Clear(); @@ -652,17 +679,14 @@ public override void OnNetworkSpawn() NetworkLog.LogWarningServer($"[{gameObject.name}][{nameof(NetworkAnimator)}] {nameof(Animator)} is not assigned! Animation synchronization will not work for this instance!"); } - if (IsServer) + m_ClientSendList = new List(128); + m_ClientRpcParams = new ClientRpcParams { - m_ClientSendList = new List(128); - m_ClientRpcParams = new ClientRpcParams + Send = new ClientRpcSendParams { - Send = new ClientRpcSendParams - { - TargetClientIds = m_ClientSendList - } - }; - } + TargetClientIds = m_ClientSendList + } + }; // Create a handler for state changes m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this); @@ -908,6 +932,13 @@ internal void CheckForAnimatorChanges() // Send an AnimationMessage only if there are dirty AnimationStates to send if (m_AnimationMessage.IsDirtyCount > 0) { +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendAnimStateRpc(m_AnimationMessage); + } + else +#endif if (!IsServer && IsOwner) { SendAnimStateServerRpc(m_AnimationMessage); @@ -932,7 +963,37 @@ private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, boo { Parameters = m_ParameterWriter.ToArray() }; - +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + if (IsOwner) + { + SendParametersUpdateRpc(parametersMessage); + } + else + { + Debug.LogError($"[{name}][Client-{NetworkManager.LocalClientId}] Attempting to send parameter updates but not the owner!"); + } + } + else + { + if (!IsServer) + { + SendParametersUpdateServerRpc(parametersMessage); + } + else + { + if (sendDirect) + { + SendParametersUpdateClientRpc(parametersMessage, clientRpcParams); + } + else + { + m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersMessage, clientRpcParams); + } + } + } +#else if (!IsServer) { SendParametersUpdateServerRpc(parametersMessage); @@ -948,6 +1009,7 @@ private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, boo m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersMessage, clientRpcParams); } } +#endif } /// @@ -1224,11 +1286,22 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame } } +#if NGO_DAMODE /// - /// Updates the client's animator's parameters + /// Distributed Authority: Updates the client's animator's parameters + /// + [Rpc(SendTo.NotAuthority)] + internal void SendParametersUpdateRpc(ParametersUpdateMessage parametersUpdate) + { + m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate); + } +#endif + + /// + /// Client-Server: Updates the client's animator's parameters /// [ClientRpc] - internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUpdate, ClientRpcParams clientRpcParams = default) + internal void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUpdate, ClientRpcParams clientRpcParams = default) { var isServerAuthoritative = IsServerAuthoritative(); if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) @@ -1242,7 +1315,7 @@ internal unsafe void SendParametersUpdateClientRpc(ParametersUpdateMessage param /// The server sets its local state and then forwards the message to the remaining clients /// [ServerRpc] - private unsafe void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpcParams serverRpcParams = default) + private void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpcParams serverRpcParams = default) { if (IsServerAuthoritative()) { @@ -1273,13 +1346,40 @@ private unsafe void SendAnimStateServerRpc(AnimationMessage animationMessage, Se } /// - /// Internally-called RPC client receiving function to update some animation state on a client + /// Client-Server: Internally-called RPC client-side receiving function to update animation states /// [ClientRpc] - internal unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default) + internal void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default) + { + ProcessAnimStates(animationMessage); + } + +#if NGO_DAMODE + /// + /// Distributed Authority: Internally-called RPC non-authority receiving function to update animation states + /// + [Rpc(SendTo.NotAuthority)] + internal void SendAnimStateRpc(AnimationMessage animationMessage) { - // This should never happen - if (IsHost) + ProcessAnimStates(animationMessage); + } +#endif + + private void ProcessAnimStates(AnimationMessage animationMessage) + { +#if NGO_DAMODE + if (HasAuthority) + { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + var hostOrOwner = NetworkManager.DistributedAuthorityMode ? "Owner" : "Host"; + var clientServerOrDAMode = NetworkManager.DistributedAuthorityMode ? "distributed authority" : "client-server"; + NetworkLog.LogWarning($"Detected the {hostOrOwner} is sending itself animation updates in {clientServerOrDAMode} mode! Please report this issue."); + } + return; + } +#else + if (NetworkManager.IsHost) { if (NetworkManager.LogLevel == LogLevel.Developer) { @@ -1287,12 +1387,15 @@ internal unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, C } return; } +#endif foreach (var animationState in animationMessage.AnimationStates) { UpdateAnimationState(animationState); } } + + /// /// Server-side trigger state update request /// The server sets its local state and then forwards the message to the remaining clients @@ -1336,8 +1439,21 @@ private void InternalSetTrigger(int hash, bool isSet = true) m_Animator.SetBool(hash, isSet); } +#if NGO_DAMODE + /// + /// Distributed Authority: Internally-called RPC client receiving function to update a trigger when the server wants to forward + /// a trigger for a client to play / reset + /// + /// the payload containing the trigger data to apply + [Rpc(SendTo.NotAuthority)] + internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage) + { + InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); + } +#endif + /// - /// Internally-called RPC client receiving function to update a trigger when the server wants to forward + /// Client Server: Internally-called RPC client receiving function to update a trigger when the server wants to forward /// a trigger for a client to play / reset /// /// the payload containing the trigger data to apply @@ -1362,15 +1478,30 @@ public void SetTrigger(string triggerName) /// sets (true) or resets (false) the trigger. The default is to set it (true). public void SetTrigger(int hash, bool setTrigger = true) { + if (!IsSpawned) + { + NetworkLog.LogError($"[{gameObject.name}] Cannot set a synchronized trigger when the {nameof(NetworkObject)} is not spawned!"); + return; + } + // MTT-3564: // After fixing the issue with trigger controlled Transitions being synchronized twice, // it exposed additional issues with this logic. Now, either the owner or the server can // update triggers. Since server-side RPCs are immediately invoked, for a host a trigger // will happen when SendAnimTriggerClientRpc is called. For a client owner, we call the // SendAnimTriggerServerRpc and then trigger locally when running in owner authority mode. + var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger }; +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode && HasAuthority) + { + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage); + InternalSetTrigger(hash, setTrigger); + } + else if (!NetworkManager.DistributedAuthorityMode && (IsOwner || IsServer)) +#else if (IsOwner || IsServer) +#endif { - var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger }; if (IsServer) { /// as to why we queue diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs index bebaf79751..5ebca355f4 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs @@ -12,10 +12,9 @@ namespace Unity.Netcode.Components [AddComponentMenu("Netcode/Network Rigidbody")] public class NetworkRigidbody : NetworkBehaviour { - /// - /// Determines if we are server (true) or owner (false) authoritative - /// - private bool m_IsServerAuthoritative; +#if NGO_DAMODE + public bool UseRigidBodyForMotion; +#endif private Rigidbody m_Rigidbody; private NetworkTransform m_NetworkTransform; @@ -24,30 +23,32 @@ public class NetworkRigidbody : NetworkBehaviour // Used to cache the authority state of this Rigidbody during the last frame private bool m_IsAuthority; - private void Awake() + protected virtual void Awake() { m_NetworkTransform = GetComponent(); - m_IsServerAuthoritative = m_NetworkTransform.IsServerAuthoritative(); - - SetupRigidBody(); + m_Rigidbody = GetComponent(); + SetupRigidbody(); } /// /// If the current has authority, /// then use the interpolation strategy, /// if the is handling interpolation, - /// set interpolation to none on the + /// set interpolation to none on the ///
/// Turn off physics for the rigid body until spawned, otherwise /// clients can run fixed update before the first /// full update ///
- private void SetupRigidBody() + private void SetupRigidbody() { - m_Rigidbody = GetComponent(); m_OriginalInterpolation = m_Rigidbody.interpolation; - - m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation); +#if NGO_DAMODE + if (m_NetworkTransform != null) + { + m_NetworkTransform.RegisterRigidbody(this, m_Rigidbody); + } +#endif m_Rigidbody.isKinematic = true; } @@ -69,28 +70,72 @@ public override void OnLostOwnership() UpdateOwnershipAuthority(); } +#if NGO_DAMODE + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + if (NetworkManager.LocalClientId == current || NetworkManager.LocalClientId == previous) + { + UpdateOwnershipAuthority(); + } + base.OnOwnershipChanged(previous, current); + } +#endif + /// /// Sets the authority differently depending upon /// whether it is server or owner authoritative /// - private void UpdateOwnershipAuthority() + internal void UpdateOwnershipAuthority() { - if (m_IsServerAuthoritative) +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) { - m_IsAuthority = NetworkManager.IsServer; + // When in distributed authority mode, always use HasAuthority + m_IsAuthority = HasAuthority; } else +#endif { - m_IsAuthority = IsOwner; + if (m_NetworkTransform.IsServerAuthoritative()) + { + m_IsAuthority = NetworkManager.IsServer; + } + else + { + m_IsAuthority = IsOwner; + } } - // If you have authority then you are not kinematic m_Rigidbody.isKinematic = !m_IsAuthority; - // Set interpolation of the Rigidbody based on authority - // With authority: let local transform handle interpolation - // Without authority: let the NetworkTransform handle interpolation - m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : RigidbodyInterpolation.None; +#if NGO_DAMODE + if (UseRigidBodyForMotion) + { + if (m_Rigidbody.isKinematic) + { + // Since we don't support kinematic extrapolation, if we are transitioning to kinematic mode + // and the user is using extrapolation on the authority side then we switch it to interpolation. + if (m_Rigidbody.interpolation == RigidbodyInterpolation.Extrapolate) + { + // Sleep until the next fixed update when switching + m_Rigidbody.Sleep(); + m_Rigidbody.interpolation = RigidbodyInterpolation.Interpolate; + } + } + else + { + // Switch it back to the original interpolation if non-kinematic (doesn't require sleep). + if (m_Rigidbody.interpolation != m_OriginalInterpolation) + { + m_Rigidbody.interpolation = m_OriginalInterpolation; + } + } + } + else +#endif + { + m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation); + } } /// @@ -102,12 +147,33 @@ public override void OnNetworkSpawn() /// public override void OnNetworkDespawn() { - m_Rigidbody.interpolation = m_OriginalInterpolation; + //m_Rigidbody.interpolation = m_OriginalInterpolation; // Turn off physics for the rigid body until spawned, otherwise // non-owners can run fixed updates before the first full // NetworkTransform update and physics will be applied (i.e. gravity, etc) m_Rigidbody.isKinematic = true; + m_Rigidbody.interpolation = m_OriginalInterpolation; + } + +#if NGO_DAMODE + /// + /// When using with a and is + /// enabled, the will update Kinematic instances using the move methods. + /// This allows one to use interpolation on both the authoritative and nonauthoritative instances. + /// + /// + /// The updates during FixedUpdate to avoid the expense of having + /// a FixedUpdate on instances that do not have a . + /// + private void FixedUpdate() + { + if (!IsSpawned || m_NetworkTransform == null || !UseRigidBodyForMotion) + { + return; + } + m_NetworkTransform.OnFixedUpdate(); } +#endif } } #endif // COM_UNITY_MODULES_PHYSICS diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs index 3a37b34b4a..67397f310e 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs @@ -21,11 +21,6 @@ public class NetworkRigidbody2D : NetworkBehaviour // Used to cache the authority state of this rigidbody during the last frame private bool m_IsAuthority; - /// - /// Gets a bool value indicating whether this on this peer currently holds authority. - /// - private bool HasAuthority => m_NetworkTransform.CanCommitToTransform; - private void Awake() { m_Rigidbody = GetComponent(); @@ -41,9 +36,9 @@ private void FixedUpdate() { if (IsSpawned) { - if (HasAuthority != m_IsAuthority) + if (m_NetworkTransform.CanCommitToTransform != m_IsAuthority) { - m_IsAuthority = HasAuthority; + m_IsAuthority = m_NetworkTransform.CanCommitToTransform; UpdateRigidbodyKinematicMode(); } } @@ -72,7 +67,7 @@ private void UpdateRigidbodyKinematicMode() /// public override void OnNetworkSpawn() { - m_IsAuthority = HasAuthority; + m_IsAuthority = m_NetworkTransform.CanCommitToTransform; m_OriginalKinematic = m_Rigidbody.isKinematic; m_OriginalInterpolation = m_Rigidbody.interpolation; UpdateRigidbodyKinematicMode(); diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index b91c70b4f1..3ddadb3369 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using Unity.Mathematics; +#if NGO_DAMODE +using Unity.Netcode.Transports.UTP; +#endif using UnityEngine; namespace Unity.Netcode.Components @@ -16,67 +20,7 @@ namespace Unity.Netcode.Components [DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts public class NetworkTransform : NetworkBehaviour { - /// - /// The default position change threshold value. - /// Any changes above this threshold will be replicated. - /// - public const float PositionThresholdDefault = 0.001f; - - /// - /// The default rotation angle change threshold value. - /// Any changes above this threshold will be replicated. - /// - public const float RotAngleThresholdDefault = 0.01f; - - /// - /// The default scale change threshold value. - /// Any changes above this threshold will be replicated. - /// - public const float ScaleThresholdDefault = 0.01f; - - /// - /// The handler delegate type that takes client requested changes and returns resulting changes handled by the server. - /// - /// The position requested by the client. - /// The rotation requested by the client. - /// The scale requested by the client. - /// The resulting position, rotation and scale changes after handling. - public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale); - - /// - /// The handler that gets invoked when server receives a change from a client. - /// This handler would be useful for server to modify pos/rot/scale before applying client's request. - /// - public OnClientRequestChangeDelegate OnClientRequestChange; - - /// - /// When set each state update will contain a state identifier - /// - internal static bool TrackByStateId; - - /// - /// Enabled by default. - /// When set (enabled by default), NetworkTransform will send common state updates using unreliable network delivery - /// to provide a higher tolerance to poor network conditions (especially packet loss). When disabled, all state updates - /// are sent using a reliable fragmented sequenced network delivery. - /// - /// - /// The following more critical state updates are still sent as reliable fragmented sequenced: - /// - The initial synchronization state update - /// - The teleporting state update. - /// - When using half float precision and the `NetworkDeltaPosition` delta exceeds the maximum delta forcing the axis in - /// question to be collapsed into the core base position, this state update will be sent as reliable fragmented sequenced. - /// - /// In order to preserve a continual consistency of axial values when unreliable delta messaging is enabled (due to the - /// possibility of dropping packets), NetworkTransform instances will send 1 axial frame synchronization update per - /// second (only for the axis marked to synchronize are sent as reliable fragmented sequenced) as long as a delta state - /// update had been previously sent. When a NetworkObject is at rest, axial frame synchronization updates are not sent. - /// - [Tooltip("When set, NetworkTransform will send common state updates using unreliable network delivery " + - "to provide a higher tolerance to poor network conditions (especially packet loss). When disabled, all state updates are " + - "sent using reliable fragmented sequenced network delivery.")] - public bool UseUnreliableDeltas = false; - + #region NETWORK TRANSFORM STATE /// /// Data structure used to synchronize the /// @@ -104,7 +48,7 @@ public struct NetworkTransformState : INetworkSerializable private const int k_ReliableSequenced = 0x00080000; private const int k_UseUnreliableDeltas = 0x00100000; private const int k_UnreliableFrameSync = 0x00200000; - private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier + private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier // Stores persistent and state relative flags private uint m_Bitset; @@ -586,7 +530,7 @@ public Quaternion GetRotation() /// /// When there is no change in an updated state's position then there are no values to return. /// Checking for is one way to detect this. - /// When used with half precision it returns the half precision delta position state update + /// When used with half precision it returns the half precision delta position state update /// which will not be the full position. /// To get a NettworkTransform's full position, use and /// pass true as the parameter. @@ -897,7 +841,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade if (HasScaleChange) { // If we are teleporting (which includes synchronizing) and the associated NetworkObject has a parent - // then we want to serialize the LossyScale since NetworkObject spawn order is not guaranteed + // then we want to serialize the LossyScale since NetworkObject spawn order is not guaranteed if (IsTeleportingNextFrame && IsParented) { serializer.SerializeValue(ref LossyScale); @@ -976,6 +920,69 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } } + #endregion + + #region PROPERTIES AND GENERAL METHODS + /// + /// The default position change threshold value. + /// Any changes above this threshold will be replicated. + /// + public const float PositionThresholdDefault = 0.001f; + + /// + /// The default rotation angle change threshold value. + /// Any changes above this threshold will be replicated. + /// + public const float RotAngleThresholdDefault = 0.01f; + + /// + /// The default scale change threshold value. + /// Any changes above this threshold will be replicated. + /// + public const float ScaleThresholdDefault = 0.01f; + + /// + /// The handler delegate type that takes client requested changes and returns resulting changes handled by the server. + /// + /// The position requested by the client. + /// The rotation requested by the client. + /// The scale requested by the client. + /// The resulting position, rotation and scale changes after handling. + public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale); + + /// + /// The handler that gets invoked when server receives a change from a client. + /// This handler would be useful for server to modify pos/rot/scale before applying client's request. + /// + public OnClientRequestChangeDelegate OnClientRequestChange; + + /// + /// When set each state update will contain a state identifier + /// + internal static bool TrackByStateId = false; + + /// + /// Enabled by default. + /// When set (enabled by default), NetworkTransform will send common state updates using unreliable network delivery + /// to provide a higher tolerance to poor network conditions (especially packet loss). When disabled, all state updates + /// are sent using a reliable fragmented sequenced network delivery. + /// + /// + /// The following more critical state updates are still sent as reliable fragmented sequenced: + /// - The initial synchronization state update + /// - The teleporting state update. + /// - When using half float precision and the `NetworkDeltaPosition` delta exceeds the maximum delta forcing the axis in + /// question to be collapsed into the core base position, this state update will be sent as reliable fragmented sequenced. + /// + /// In order to preserve a continual consistency of axial values when unreliable delta messaging is enabled (due to the + /// possibility of dropping packets), NetworkTransform instances will send 1 axial frame synchronization update per + /// second (only for the axis marked to synchronize are sent as reliable fragmented sequenced) as long as a delta state + /// update had been previously sent. When a NetworkObject is at rest, axial frame synchronization updates are not sent. + /// + [Tooltip("When set, NetworkTransform will send common state updates using unreliable network delivery " + + "to provide a higher tolerance to poor network conditions (especially packet loss). When disabled, all state updates are " + + "sent using reliable fragmented sequenced network delivery.")] + public bool UseUnreliableDeltas = false; /// /// When enabled (default), the x component of position will be synchronized by authority. @@ -1320,6 +1327,9 @@ internal NetworkTransformState LocalAuthoritativeNetworkState private BufferedLinearInterpolatorVector3 m_ScaleInterpolator; private BufferedLinearInterpolatorQuaternion m_RotationInterpolator; // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + // The previous network state + private NetworkTransformState m_OldState = new NetworkTransformState(); + // Non-Authoritative's current position, scale, and rotation that is used to assure the non-authoritative side cannot make adjustments to // the portions of the transform being synchronized. private Vector3 m_CurrentPosition; @@ -1328,23 +1338,22 @@ internal NetworkTransformState LocalAuthoritativeNetworkState private Vector3 m_TargetScale; private Quaternion m_CurrentRotation; private Vector3 m_TargetRotation; +#if NGO_DAMODE + private bool m_UseRigidbodyForMotion; + private Rigidbody m_RigidbodyInternal; + private NetworkRigidbody m_NetworkRigidbody; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void UpdatePositionInterpolator(Vector3 position, double time, bool resetInterpolator = false) + internal void RegisterRigidbody(NetworkRigidbody networkRigidbody, Rigidbody rigidbody) { - if (!CanCommitToTransform) + if (networkRigidbody != null) { - if (resetInterpolator) - { - m_PositionInterpolator.ResetTo(position, time); - } - else - { - m_PositionInterpolator.AddMeasurement(position, time); - } + m_NetworkRigidbody = networkRigidbody; + m_UseRigidbodyForMotion = m_NetworkRigidbody.UseRigidBodyForMotion; + m_RigidbodyInternal = rigidbody; } } +#endif + #if DEBUG_NETWORKTRANSFORM || UNITY_INCLUDE_TESTS /// @@ -1402,8 +1411,13 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) { if (!IsServerAuthoritative() && NetworkObject.OwnerClientId == targetClientId) { +#if NGO_DAMODE + // In distributed authority mode we want to synchronize the half float if we are the owner. + return (!NetworkManager.DistributedAuthorityMode && NetworkObject.IsOwnedByServer) || (NetworkManager.DistributedAuthorityMode); +#else // Return false for all client owners but return true for the server return NetworkObject.IsOwnedByServer; +#endif } return true; } @@ -1411,6 +1425,11 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) // For test logging purposes internal NetworkTransformState SynchronizeState; + // DANGO-TODO: We will want to remove this when we migrate NetworkTransforms to a dedicated internal message + private const ushort k_NetworkTransformStateMagic = 0xf48d; + #endregion + + #region ONSYNCHRONIZE /// /// This is invoked when a new client joins (server and client sides) /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState) @@ -1431,15 +1450,26 @@ protected override void OnSynchronize(ref BufferSerializer serializer) HalfVectorRotation = new HalfVector4(), HalfVectorScale = new HalfVector3(), NetworkDeltaPosition = new NetworkDeltaPosition(), + }; if (serializer.IsWriter) { +#if NGO_DAMODE + // DANGO-TODO: This magic value is sent to the server in order to identify the network transform. + // The server discards it before forwarding synchronization data to other clients. + if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + { + var writer = serializer.GetFastBufferWriter(); + writer.WriteValueSafe(k_NetworkTransformStateMagic); + } +#endif + synchronizationState.IsTeleportingNextFrame = true; var transformToCommit = transform; // If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for // for the non-authority side to be able to properly synchronize delta position updates. - ApplyTransformToNetworkStateWithInfo(ref synchronizationState, ref transformToCommit, true, targetClientId); + CheckForStateChange(ref synchronizationState, ref transformToCommit, true, targetClientId); synchronizationState.NetworkSerialize(serializer); SynchronizeState = synchronizationState; } @@ -1463,7 +1493,9 @@ protected override void OnSynchronize(ref BufferSerializer serializer) m_LocalAuthoritativeNetworkState.IsSynchronizing = false; } } + #endregion + #region AUTHORITY STATE UPDATE /// /// This will try to send/commit the current transform delta states (if any) /// @@ -1543,7 +1575,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz } // If the transform has deltas (returns dirty) or if an explicitly set state is pending - if (m_LocalAuthoritativeNetworkState.ExplicitSet || ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize)) + if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize)) { m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OldState.LastSerializedSize; @@ -1589,20 +1621,6 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz } } - /// - /// Initializes the interpolators with the current transform values - /// - private void ResetInterpolatedStateToCurrentAuthoritativeState() - { - var serverTime = NetworkManager.ServerTime.Time; - - UpdatePositionInterpolator(GetSpaceRelativePosition(), serverTime, true); - UpdatePositionSlerp(); - - m_ScaleInterpolator.ResetTo(transform.localScale, serverTime); - m_RotationInterpolator.ResetTo(GetSpaceRelativeRotation(), serverTime); - } - /// /// Used for integration testing: /// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed dirty information returned @@ -1617,7 +1635,7 @@ internal NetworkTransformState ApplyLocalNetworkState(Transform transform) m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); // Now check the transform for any threshold value changes - ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transform); + CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transform); // Return the entire state to be used by the integration test return m_LocalAuthoritativeNetworkState; @@ -1637,14 +1655,14 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat networkState.UseUnreliableDeltas = UseUnreliableDeltas; m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); - return ApplyTransformToNetworkStateWithInfo(ref networkState, ref transformToUse); + return CheckForStateChange(ref networkState, ref transformToUse); } /// /// Applies the transform to the specified. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, ref Transform transformToUse, bool isSynchronization = false, ulong targetClientId = 0) + private bool CheckForStateChange(ref NetworkTransformState networkState, ref Transform transformToUse, bool isSynchronization = false, ulong targetClientId = 0) { // As long as we are not doing our first synchronization and we are sending unreliable deltas, each // NetworkTransform will stagger its full transfom synchronization over a 1 second period based on the @@ -1681,6 +1699,7 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw var scale = transformToUse.localScale; networkState.IsSynchronizing = isSynchronization; + // Check for parenting when synchronizing and/or teleporting if (isSynchronization || networkState.IsTeleportingNextFrame) { @@ -1960,8 +1979,8 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw } } - // For scale, we need to check for parenting when synchronizing and/or teleporting - if (isSynchronization || networkState.IsTeleportingNextFrame) + // For scale, we need to check for parenting when synchronizing and/or teleporting (synchronization is always teleporting) + if (networkState.IsTeleportingNextFrame) { // If we are synchronizing and the associated NetworkObject has a parent then we want to send the // LossyScale if the NetworkObject has a parent since NetworkObject spawn order is not guaranteed @@ -2047,6 +2066,49 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw return isDirty; } + + /// + /// Authority subscribes to network tick events and will invoke + /// each network tick. + /// + private void OnNetworkTick() + { + // As long as we are still authority + if (CanCommitToTransform) + { + // Update any changes to the transform + var transformSource = transform; + OnUpdateAuthoritativeState(ref transformSource); + + m_CurrentPosition = GetSpaceRelativePosition(); + m_TargetPosition = GetSpaceRelativePosition(); + } + else // If we are no longer authority, unsubscribe to the tick event + if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) + { + NetworkManager.NetworkTickSystem.Tick -= OnNetworkTick; + } + } + #endregion + + #region NON-AUTHORITY STATE UPDATE + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void UpdatePositionInterpolator(Vector3 position, double time, bool resetInterpolator = false) + { + if (!CanCommitToTransform) + { + if (resetInterpolator) + { + m_PositionInterpolator.ResetTo(position, time); + } + else + { + m_PositionInterpolator.AddMeasurement(position, time); + } + } + } + /// /// Applies the authoritative state to the transform /// @@ -2056,8 +2118,13 @@ private void ApplyAuthoritativeState() // The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated // at the end of this method and assure that when not interpolating the non-authoritative side // cannot make adjustments to any portions the transform not being synchronized. +#if NGO_DAMODE + var adjustedPosition = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : m_CurrentPosition; + var adjustedRotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : m_CurrentRotation; +#else var adjustedPosition = m_CurrentPosition; var adjustedRotation = m_CurrentRotation; +#endif var adjustedRotAngles = adjustedRotation.eulerAngles; var adjustedScale = m_CurrentScale; @@ -2183,13 +2250,23 @@ private void ApplyAuthoritativeState() { m_CurrentPosition = adjustedPosition; } - if (InLocalSpace) +#if NGO_DAMODE + if (m_UseRigidbodyForMotion) { - transform.localPosition = m_CurrentPosition; + m_RigidbodyInternal.MovePosition(m_CurrentPosition); + transform.position = m_RigidbodyInternal.position; } else +#endif { - transform.position = m_CurrentPosition; + if (InLocalSpace) + { + transform.localPosition = m_CurrentPosition; + } + else + { + transform.position = m_CurrentPosition; + } } } @@ -2201,13 +2278,24 @@ private void ApplyAuthoritativeState() { m_CurrentRotation = adjustedRotation; } - if (InLocalSpace) + +#if NGO_DAMODE + if (m_UseRigidbodyForMotion) { - transform.localRotation = m_CurrentRotation; + m_RigidbodyInternal.MoveRotation(m_CurrentRotation); + transform.rotation = m_RigidbodyInternal.rotation; } else +#endif { - transform.rotation = m_CurrentRotation; + if (InLocalSpace) + { + transform.localRotation = m_CurrentRotation; + } + else + { + transform.rotation = m_CurrentRotation; + } } } @@ -2313,6 +2401,13 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.position = currentPosition; } +#if NGO_DAMODE + if (m_UseRigidbodyForMotion) + { + m_RigidbodyInternal.position = transform.position; + } +#endif + if (Interpolate) { UpdatePositionInterpolator(currentPosition, sentTime, true); @@ -2407,6 +2502,14 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.rotation = currentRotation; } +#if NGO_DAMODE + if (m_UseRigidbodyForMotion) + { + m_RigidbodyInternal.rotation = transform.rotation; + m_RigidbodyInternal.MoveRotation(transform.rotation); + } +#endif + if (Interpolate) { m_RotationInterpolator.ResetTo(currentRotation, sentTime); @@ -2451,7 +2554,6 @@ private void ApplyUpdatedState(NetworkTransformState newState) var sentTime = newState.SentTime; var currentRotation = GetSpaceRelativeRotation(); - var currentEulerAngles = currentRotation.eulerAngles; // Only if using half float precision and our position had changed last update then if (UseHalfFloatPrecision && m_LocalAuthoritativeNetworkState.HasPositionChange) @@ -2551,7 +2653,7 @@ private void ApplyUpdatedState(NetworkTransformState newState) } else { - currentEulerAngles = m_TargetRotation; + var currentEulerAngles = m_TargetRotation; // Adjust based on which axis changed // (both half precision and full precision apply Eulers to the RotAngle properties when reading the update) if (m_LocalAuthoritativeNetworkState.HasRotAngleX) @@ -2586,7 +2688,6 @@ protected virtual void OnNetworkTransformStateUpdated(ref NetworkTransformState } - private NetworkTransformState m_OldState = new NetworkTransformState(); /// /// Only non-authoritative instances should invoke this method @@ -2632,18 +2733,6 @@ public void SetMaxInterpolationBound(float maxInterpolationBound) m_ScaleInterpolator.MaxInterpolationBound = maxInterpolationBound; } - /// - /// Create interpolators when first instantiated to avoid memory allocations if the - /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) - /// - protected virtual void Awake() - { - // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value - m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); - m_PositionInterpolator = new BufferedLinearInterpolatorVector3(); - m_ScaleInterpolator = new BufferedLinearInterpolatorVector3(); - } - /// /// Checks for changes in the axis to synchronize. If one or more did change it /// then determines if the axis were enabled and if the delta between the last known @@ -2714,28 +2803,19 @@ internal void OnUpdateAuthoritativeState(ref Transform transformSource) TryCommitTransform(ref transformSource); } + #endregion + #region SPAWN, DESPAWN, AND INITIALIZATION /// - /// Authority subscribes to network tick events and will invoke - /// each network tick. + /// Create interpolators when first instantiated to avoid memory allocations if the + /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) /// - private void NetworkTickSystem_Tick() + protected virtual void Awake() { - // As long as we are still authority - if (CanCommitToTransform) - { - // Update any changes to the transform - var transformSource = transform; - OnUpdateAuthoritativeState(ref transformSource); - - m_CurrentPosition = GetSpaceRelativePosition(); - m_TargetPosition = GetSpaceRelativePosition(); - } - else // If we are no longer authority, unsubscribe to the tick event - if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) - { - NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; - } + // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); + m_PositionInterpolator = new BufferedLinearInterpolatorVector3(); + m_ScaleInterpolator = new BufferedLinearInterpolatorVector3(); } /// @@ -2750,52 +2830,33 @@ public override void OnNetworkSpawn() m_CachedNetworkManager = NetworkManager; Initialize(); - } - - /// - public override void OnNetworkDespawn() - { - DeregisterForTickUpdate(this); - CanCommitToTransform = false; - if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) +#if NGO_DAMODE + if (CanCommitToTransform) { - NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; + SetState(GetSpaceRelativePosition(), GetSpaceRelativeRotation(), GetScale(), false); } +#endif } - /// - public override void OnDestroy() + private void CleanUpOnDestroyOrDespawn() { - // During destroy, use NetworkBehaviour.NetworkManager as opposed to m_CachedNetworkManager - if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) - { - NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; - } + DeregisterForTickUpdate(this); CanCommitToTransform = false; - base.OnDestroy(); } /// - public override void OnLostOwnership() + public override void OnNetworkDespawn() { - base.OnLostOwnership(); + CleanUpOnDestroyOrDespawn(); + base.OnNetworkDespawn(); } /// - public override void OnGainedOwnership() - { - base.OnGainedOwnership(); - } - - protected override void OnOwnershipChanged(ulong previous, ulong current) + public override void OnDestroy() { - // If we were the previous owner or the newly assigned owner then reinitialize - if (current == m_CachedNetworkManager.LocalClientId || previous == m_CachedNetworkManager.LocalClientId) - { - InternalInitialization(true); - } - base.OnOwnershipChanged(previous, current); + CleanUpOnDestroyOrDespawn(); + base.OnDestroy(); } /// @@ -2806,10 +2867,6 @@ protected virtual void OnInitialize(ref NetworkTransformState replicatedState) { } - /// - /// An owner read and owner write NetworkVariable so it doesn't generate any messages - /// - private NetworkVariable m_InternalStatNetVar = new NetworkVariable(default, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner); /// /// This method is only invoked by the owner /// Use: OnInitialize(ref NetworkTransformState replicatedState) to be notified on all instances @@ -2822,6 +2879,27 @@ protected virtual void OnInitialize(ref NetworkVariable r private int m_HalfFloatTargetTickOwnership; + /// + /// Initializes the interpolators with the current transform values + /// + private void ResetInterpolatedStateToCurrentAuthoritativeState() + { + var serverTime = NetworkManager.ServerTime.Time; +#if NGO_DAMODE + var position = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : GetSpaceRelativePosition(); + var rotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : GetSpaceRelativeRotation(); +#else + var position = GetSpaceRelativePosition(); + var rotation = GetSpaceRelativeRotation(); +#endif + + UpdatePositionInterpolator(position, serverTime, true); + UpdatePositionSlerp(); + + m_ScaleInterpolator.ResetTo(transform.localScale, serverTime); + m_RotationInterpolator.ResetTo(rotation, serverTime); + } + /// /// The internal initialzation method to allow for internal API adjustments /// @@ -2837,6 +2915,26 @@ private void InternalInitialization(bool isOwnershipChange = false) var currentPosition = GetSpaceRelativePosition(); var currentRotation = GetSpaceRelativeRotation(); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + RegisterNetworkManagerForTickUpdate(NetworkManager); + m_NetworkTransformTickRegistration = s_NetworkTickRegistration[m_CachedNetworkManager]; + } + + // Depending upon order of operations, we invoke this in order to assure that proper settings are applied. + if (m_NetworkRigidbody) + { + m_NetworkRigidbody.UpdateOwnershipAuthority(); + } + + if (m_UseRigidbodyForMotion) + { + m_RigidbodyInternal.position = currentPosition; + m_RigidbodyInternal.rotation = currentRotation; + } +#endif + if (CanCommitToTransform) { if (UseHalfFloatPrecision) @@ -2870,12 +2968,6 @@ private void InternalInitialization(bool isOwnershipChange = false) } OnInitialize(ref m_LocalAuthoritativeNetworkState); - - if (IsOwner) - { - m_InternalStatNetVar.Value = m_LocalAuthoritativeNetworkState; - OnInitialize(ref m_InternalStatNetVar); - } } /// @@ -2885,6 +2977,30 @@ protected void Initialize() { InternalInitialization(); } + #endregion + + #region PARENTING AND OWNERSHIP + /// + public override void OnLostOwnership() + { + base.OnLostOwnership(); + } + + /// + public override void OnGainedOwnership() + { + base.OnGainedOwnership(); + } + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + // If we were the previous owner or the newly assigned owner then reinitialize + if (current == m_CachedNetworkManager.LocalClientId || previous == m_CachedNetworkManager.LocalClientId) + { + InternalInitialization(true); + } + base.OnOwnershipChanged(previous, current); + } /// /// @@ -2919,7 +3035,9 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj } base.OnNetworkObjectParentChanged(parentNetworkObject); } + #endregion + #region API STATE UPDATE METHODS /// /// Directly sets a state on the authoritative transform. /// Owner clients can directly set the state on a server authoritative transform @@ -2990,6 +3108,15 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s { transform.SetPositionAndRotation(pos, rot); } + +#if NGO_DAMODE + if (m_UseRigidbodyForMotion && shouldTeleport) + { + m_RigidbodyInternal.position = transform.position; + m_RigidbodyInternal.rotation = transform.rotation; + } +#endif + transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; @@ -3002,7 +3129,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s var explicitState = m_LocalAuthoritativeNetworkState.ExplicitSet; // Apply any delta states to the m_LocalAuthoritativeNetworkState - var isDirty = ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transformToCommit); + var isDirty = CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit); // If we were dirty and the explicit state was set (prior to checking for deltas) or the current explicit state is dirty, // then we set the explicit state flag. @@ -3045,21 +3172,57 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool SetStateInternal(pos, rot, scale, shouldTeleport); } + /// + /// Teleport the transform to the given values without interpolating + /// + /// new position to move to. + /// new rotation to rotate to. + /// new scale to scale to. + /// + public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale) + { + if (!CanCommitToTransform) + { + throw new Exception("Teleporting on non-authoritative side is not allowed!"); + } + + // Teleporting now is as simple as setting the internal state and passing the teleport flag + SetStateInternal(newPosition, newRotation, newScale, true); + } + #endregion + #region UPDATES AND AUTHORITY CHECKS +#if NGO_DAMODE + private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; +#endif private void UpdateInterpolation() { // Non-Authority if (Interpolate) { var serverTime = m_CachedNetworkManager.ServerTime; - var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; var cachedServerTime = serverTime.Time; - + var offset = 0f; +#if NGO_DAMODE + var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; // With owner authoritative mode, non-authority clients can lag behind // by more than 1 tick period of time. The current "solution" for now // is to make their cachedRenderTime run 2 ticks behind. - var ticksAgo = !IsServerAuthoritative() && !IsServer ? 2 : 1; - var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; + var ticksAgo = (!IsServerAuthoritative() && !IsServer) || m_UseRigidbodyForMotion ? 2 : 1; + if (m_CachedNetworkManager.DistributedAuthorityMode) + { + ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); + offset = m_NetworkTransformTickRegistration.Offset; + } +#else + // With owner authoritative mode, non-authority clients can lag behind + // by more than 1 tick period of time. The current "solution" for now + // is to make their cachedRenderTime run 2 ticks behind. + var ticksAgo = (!IsServerAuthoritative() && !IsServer) ? 2 : 1; + var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; +#endif + + var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo, offset).Time; // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) @@ -3091,35 +3254,49 @@ private void UpdateInterpolation() protected virtual void Update() { // If not spawned or this instance has authority, exit early +#if NGO_DAMODE + if (!IsSpawned || CanCommitToTransform || m_UseRigidbodyForMotion) +#else if (!IsSpawned || CanCommitToTransform) +#endif { return; } - // Non-Authority + // Update interpolation UpdateInterpolation(); // Apply the current authoritative state ApplyAuthoritativeState(); } +#if NGO_DAMODE /// - /// Teleport the transform to the given values without interpolating + /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, + /// this will be invoked during . /// - /// new position to move to. - /// new rotation to rotate to. - /// new scale to scale to. - /// - public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale) + internal void OnFixedUpdate() { - if (!CanCommitToTransform) + // If not spawned or this instance has authority, exit early + if (!m_UseRigidbodyForMotion || !IsSpawned || CanCommitToTransform) { - throw new Exception("Teleporting on non-authoritative side is not allowed!"); + return; } - // Teleporting now is as simple as setting the internal state and passing the teleport flag - SetStateInternal(newPosition, newRotation, newScale, true); + if (m_RigidbodyInternal.IsSleeping()) + { + m_RigidbodyInternal.WakeUp(); + } + + // Update interpolation + UpdateInterpolation(); + + // Apply the current authoritative state + ApplyAuthoritativeState(); } +#endif + + /// /// Override this method and return false to switch to owner authoritative mode @@ -3127,6 +3304,12 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca /// ( or ) where when false it runs as owner-client authoritative protected virtual bool OnIsServerAuthoritative() { +#if NGO_DAMODE + if (m_CachedNetworkManager) + { + return !m_CachedNetworkManager.DistributedAuthorityMode; + } +#endif return true; } @@ -3142,6 +3325,9 @@ public bool IsServerAuthoritative() return OnIsServerAuthoritative(); } + #endregion + + #region MESSAGE HANDLING /// /// Invoked by to update the transform state /// @@ -3183,9 +3369,15 @@ private void UpdateTransformState() { NetworkObjectId = NetworkObjectId, NetworkBehaviourId = NetworkBehaviourId, - State = m_LocalAuthoritativeNetworkState + State = m_LocalAuthoritativeNetworkState, +#if NGO_DAMODE + DistributedAuthorityMode = m_CachedNetworkManager.DistributedAuthorityMode, + // Don't populate if we are the DAHost as we send directly to each client + TargetIds = m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.DAHost ? NetworkObject.Observers.ToArray() : null, +#endif }; + // Determine what network delivery method to use: // When to send reliable packets: // - If UsUnrealiable is not enabled @@ -3195,7 +3387,7 @@ private void UpdateTransformState() | m_LocalAuthoritativeNetworkState.UnreliableFrameSync | m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; - // Server-host always sends updates to all clients (but itself) + // Server-host-dahost always sends updates to all clients (but itself) if (IsServer) { var clientCount = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList.Count; @@ -3219,10 +3411,48 @@ private void UpdateTransformState() NetworkManager.MessageManager.SendMessage(ref networkTransformMessage, networkDelivery, NetworkManager.ServerClientId); } } + #endregion - #region Network Tick Registration and Handling + #region NETWORK TICK REGISTRATOIN AND HANDLING private static Dictionary s_NetworkTickRegistration = new Dictionary(); +#if NGO_DAMODE + internal static float GetTickLatency(NetworkManager networkManager) + { + if (s_NetworkTickRegistration.ContainsKey(networkManager)) + { + return s_NetworkTickRegistration[networkManager].TicksAgo; + } + return 0f; + } + + /// + /// Returns the number of ticks (fractional) a client is latent relative + /// to its current RTT. + /// + public static float GetTickLatency() + { + return GetTickLatency(NetworkManager.Singleton); + } + + internal static float GetTickLatencyInSeconds(NetworkManager networkManager) + { + if (s_NetworkTickRegistration.ContainsKey(networkManager)) + { + return s_NetworkTickRegistration[networkManager].TicksAgoInSeconds(); + } + return 0f; + } + + /// + /// Returns the tick latency in seconds (typically fractional) + /// + public static float GetTickLatencyInSeconds() + { + return GetTickLatencyInSeconds(NetworkManager.Singleton); + } +#endif + private static void RemoveTickUpdate(NetworkManager networkManager) { s_NetworkTickRegistration.Remove(networkManager); @@ -3252,12 +3482,41 @@ public void Remove() RemoveTickUpdate(m_NetworkManager); } +#if NGO_DAMODE + internal float TicksAgoInSeconds() + { + return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; + } +#endif + /// /// Invoked once per network tick, this will update any registered /// authority instances. /// private void TickUpdate() { +#if NGO_DAMODE + if (m_UnityTransport != null) + { + // Determine the desired ticks ago by the RTT (this really should be the combination of the + // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor + // network quality so latent interpolation is going to be expected). + var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); + m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); + var tickAgoSum = 0.0f; + foreach (var tickAgo in m_TicksAgoSamples) + { + tickAgoSum += tickAgo; + } + m_PreviousTicksAgo = TicksAgo; + TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); + m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; + // Get the partial tick value for when this is all calculated to provide an offset for determining + // the relative starting interpolation point for the next update + Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); + } +#endif + // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before if (m_NetworkManager.ServerTime.Tick <= m_LastTick) { @@ -3267,17 +3526,48 @@ private void TickUpdate() { if (networkTransform.IsSpawned) { - networkTransform.NetworkTickSystem_Tick(); + networkTransform.OnNetworkTick(); } } m_LastTick = m_NetworkManager.ServerTime.Tick; } +#if NGO_DAMODE + private UnityTransport m_UnityTransport; + private float m_TickFrequency; + private float m_OffsetTickFrequency; + private ulong m_TickInMS; + private int m_TickSampleIndex; + private int m_TickRate; + public float TicksAgo { get; private set; } + public float Offset { get; private set; } + private float m_PreviousTicksAgo; + + private List m_TicksAgoSamples = new List(); +#endif + public NetworkTransformTickRegistration(NetworkManager networkManager) { m_NetworkManager = networkManager; m_NetworkTickUpdate = new Action(TickUpdate); networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate; +#if NGO_DAMODE + m_TickRate = (int)m_NetworkManager.NetworkConfig.TickRate; + m_TickFrequency = 1.0f / m_TickRate; + // For the offset, it uses the fractional remainder of the tick to determine the offset. + // In order to keep within tick boundaries, we increment the tick rate by 1 to assure it + // will always be < the tick frequency. + m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); + m_TickInMS = (ulong)(1000 * m_TickFrequency); + m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; + // Fill the sample with a starting value of 1 + for (int i = 0; i < m_TickRate; i++) + { + m_TicksAgoSamples.Add(1f); + } + TicksAgo = 1f; + m_PreviousTicksAgo = 1f; +#endif if (networkManager.IsServer) { networkManager.OnServerStopped += OnNetworkManagerStopped; @@ -3297,6 +3587,16 @@ internal void RegisterForTickSynchronization() m_NextTickSync = NetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); } +#if NGO_DAMODE + private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkManager) + { + if (!s_NetworkTickRegistration.ContainsKey(networkManager)) + { + s_NetworkTickRegistration.Add(networkManager, new NetworkTransformTickRegistration(networkManager)); + } + } +#endif + /// /// Will register the NetworkTransform instance for the single tick update entry point. /// If a NetworkTransformTickRegistration has not yet been registered for the NetworkManager @@ -3305,32 +3605,51 @@ internal void RegisterForTickSynchronization() /// private static void RegisterForTickUpdate(NetworkTransform networkTransform) { +#if NGO_DAMODE + if (!networkTransform.NetworkManager.DistributedAuthorityMode && !s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) + { + s_NetworkTickRegistration.Add(networkTransform.NetworkManager, new NetworkTransformTickRegistration(networkTransform.NetworkManager)); + } +#else if (!s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) { s_NetworkTickRegistration.Add(networkTransform.NetworkManager, new NetworkTransformTickRegistration(networkTransform.NetworkManager)); } +#endif + networkTransform.RegisterForTickSynchronization(); s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Add(networkTransform); } /// /// If a NetworkTransformTickRegistration exists for the NetworkManager instance, then this will - /// remove the NetworkTransform instance from the single tick update entry point. + /// remove the NetworkTransform instance from the single tick update entry point. /// /// private static void DeregisterForTickUpdate(NetworkTransform networkTransform) { + if (networkTransform.NetworkManager == null) + { + return; + } if (s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) { s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Remove(networkTransform); +#if NGO_DAMODE + if (!networkTransform.NetworkManager.DistributedAuthorityMode && s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Count == 0) + { + var registrationEntry = s_NetworkTickRegistration[networkTransform.NetworkManager]; + registrationEntry.Remove(); + } +#else if (s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Count == 0) { var registrationEntry = s_NetworkTickRegistration[networkTransform.NetworkManager]; registrationEntry.Remove(); } +#endif } } - #endregion } diff --git a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs new file mode 100644 index 0000000000..82563667ef --- /dev/null +++ b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs @@ -0,0 +1,230 @@ +#if NGO_DAMODE +using System.Collections.Generic; +using Unity.Collections; +using Unity.Jobs; +using UnityEngine; + +namespace Unity.Netcode.Components +{ + public interface IContactEventHandler + { + Rigidbody GetRigidbody(); + + void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default); + } + + [AddComponentMenu("Netcode/Rigidbody Contact Event Manager")] + public class RigidbodyContactEventManager : MonoBehaviour + { + public static RigidbodyContactEventManager Instance { get; private set; } + + private struct JobResultStruct + { + public bool HasCollisionStay; + public int ThisInstanceID; + public int OtherInstanceID; + public Vector3 AverageNormal; + public Vector3 AverageCollisionStayNormal; + public Vector3 ContactPoint; + } + + private NativeArray m_ResultsArray; + private int m_Count = 0; + private JobHandle m_JobHandle; + + private readonly Dictionary m_RigidbodyMapping = new Dictionary(); + private readonly Dictionary m_HandlerMapping = new Dictionary(); + + private void OnEnable() + { + m_ResultsArray = new NativeArray(16, Allocator.Persistent); + Physics.ContactEvent += Physics_ContactEvent; + if (Instance != null) + { + NetworkLog.LogError($"[Invalid][Multiple Instances] Found more than one instance of {nameof(RigidbodyContactEventManager)}: {name} and {Instance.name}"); + NetworkLog.LogError($"[Disable][Additional Instance] Disabling {name} instance!"); + gameObject.SetActive(false); + return; + } + Instance = this; + } + + public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true) + { + var rigidbody = contactEventHandler.GetRigidbody(); + var instanceId = rigidbody.GetInstanceID(); + if (register) + { + if (!m_RigidbodyMapping.ContainsKey(instanceId)) + { + m_RigidbodyMapping.Add(instanceId, rigidbody); + } + + if (!m_HandlerMapping.ContainsKey(instanceId)) + { + m_HandlerMapping.Add(instanceId, contactEventHandler); + } + } + else + { + m_RigidbodyMapping.Remove(instanceId); + m_HandlerMapping.Remove(instanceId); + } + } + + private void OnDisable() + { + m_JobHandle.Complete(); + m_ResultsArray.Dispose(); + + Physics.ContactEvent -= Physics_ContactEvent; + + m_RigidbodyMapping.Clear(); + Instance = null; + } + + private bool m_HasCollisions; + private int m_CurrentCount = 0; + + private void ProcessCollisions() + { + // Process all collisions + for (int i = 0; i < m_Count; i++) + { + var thisInstanceID = m_ResultsArray[i].ThisInstanceID; + var otherInstanceID = m_ResultsArray[i].OtherInstanceID; + var rb0Valid = thisInstanceID != 0 && m_RigidbodyMapping.ContainsKey(thisInstanceID); + var rb1Valid = otherInstanceID != 0 && m_RigidbodyMapping.ContainsKey(otherInstanceID); + // Only notify registered rigid bodies. + if (!rb0Valid || !rb1Valid || !m_HandlerMapping.ContainsKey(thisInstanceID)) + { + continue; + } + if (m_ResultsArray[i].HasCollisionStay) + { + m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal); + } + else + { + m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint); + } + } + } + + private void FixedUpdate() + { + // Only process new collisions + if (!m_HasCollisions && m_CurrentCount == 0) + { + return; + } + + // This assures we won't process the same collision + // set after it has been processed. + if (m_HasCollisions) + { + m_CurrentCount = m_Count; + m_HasCollisions = false; + m_JobHandle.Complete(); + } + ProcessCollisions(); + } + + private void LateUpdate() + { + m_CurrentCount = 0; + } + + private ulong m_EventId; + private void Physics_ContactEvent(PhysicsScene scene, NativeArray.ReadOnly pairHeaders) + { + m_EventId++; + m_HasCollisions = true; + int n = pairHeaders.Length; + if (m_ResultsArray.Length < n) + { + m_ResultsArray.Dispose(); + m_ResultsArray = new NativeArray(Mathf.NextPowerOfTwo(n), Allocator.Persistent); + } + m_Count = n; + var job = new GetCollisionsJob() + { + PairedHeaders = pairHeaders, + ResultsArray = m_ResultsArray + }; + m_JobHandle = job.Schedule(n, 256); + } + + private struct GetCollisionsJob : IJobParallelFor + { + [ReadOnly] + public NativeArray.ReadOnly PairedHeaders; + + public NativeArray ResultsArray; + + public void Execute(int index) + { + Vector3 averageNormal = Vector3.zero; + Vector3 averagePoint = Vector3.zero; + Vector3 averageCollisionStay = Vector3.zero; + int count = 0; + int collisionStaycount = 0; + int positionCount = 0; + for (int j = 0; j < PairedHeaders[index].PairCount; j++) + { + ref readonly var pair = ref PairedHeaders[index].GetContactPair(j); + + if (pair.IsCollisionExit) + { + continue; + } + + for (int k = 0; k < pair.ContactCount; k++) + { + ref readonly var contact = ref pair.GetContactPoint(k); + averagePoint += contact.Position; + positionCount++; + if (!pair.IsCollisionStay) + { + averageNormal += contact.Normal; + count++; + } + else + { + averageCollisionStay += contact.Normal; + collisionStaycount++; + } + } + } + + if (count != 0) + { + averageNormal /= count; + } + + if (collisionStaycount != 0) + { + averageCollisionStay /= collisionStaycount; + } + + if (positionCount != 0) + { + averagePoint /= positionCount; + } + + var result = new JobResultStruct() + { + ThisInstanceID = PairedHeaders[index].BodyInstanceID, + OtherInstanceID = PairedHeaders[index].OtherBodyInstanceID, + AverageNormal = averageNormal, + HasCollisionStay = collisionStaycount != 0, + AverageCollisionStayNormal = averageCollisionStay, + ContactPoint = averagePoint + }; + + ResultsArray[index] = result; + } + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs.meta b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs.meta new file mode 100644 index 0000000000..c0f2a00237 --- /dev/null +++ b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 739e5cee846b6384988f9a47e4691836 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 974b492e6c..2670c171f0 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -2654,6 +2654,9 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Execute)); instructions.Add(processor.Create(OpCodes.Ceq)); +#if NGO_DAMODE + instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); +#else instructions.Add(processor.Create(OpCodes.Brfalse, returnInstr)); // if (networkManager.IsServer || networkManager.IsHost) -> ServerRpc @@ -2664,6 +2667,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); +#endif instructions.Add(returnInstr); instructions.Add(lastInstr); diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs index ce8023bd6b..8a814ac6d6 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs @@ -19,7 +19,7 @@ public static SettingsProvider CreateNetcodeSettingsProvider() { // First parameter is the path in the Settings window. // Second parameter is the scope of this setting: it only appears in the Settings window for the Project scope. - var provider = new SettingsProvider("Project/NetcodeForGameObjects", SettingsScope.Project) + var provider = new SettingsProvider("Project/Multiplayer/NetcodeForGameObjects", SettingsScope.Project) { label = "Netcode for GameObjects", keywords = new[] { "netcode", "editor" }, diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 1713ba226a..5883f59967 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -1,6 +1,3 @@ -#if UNITY_2022_3_OR_NEWER && (RELAY_SDK_INSTALLED && !UNITY_WEBGL ) || (RELAY_SDK_INSTALLED && UNITY_WEBGL && UTP_TRANSPORT_2_0_ABOVE) -#define RELAY_INTEGRATION_AVAILABLE -#endif using System; using System.Collections.Generic; using System.IO; @@ -33,7 +30,9 @@ public class NetworkManagerEditor : UnityEditor.Editor private SerializedProperty m_ProtocolVersionProperty; private SerializedProperty m_NetworkTransportProperty; private SerializedProperty m_TickRateProperty; - private SerializedProperty m_MaxObjectUpdatesPerTickProperty; +#if NGO_DAMODE + private SerializedProperty m_SessionModeProperty; +#endif private SerializedProperty m_ClientConnectionBufferTimeoutProperty; private SerializedProperty m_ConnectionApprovalProperty; private SerializedProperty m_EnsureNetworkVariableLengthSafetyProperty; @@ -41,6 +40,7 @@ public class NetworkManagerEditor : UnityEditor.Editor private SerializedProperty m_EnableSceneManagementProperty; private SerializedProperty m_RecycleNetworkIdsProperty; private SerializedProperty m_NetworkIdRecycleDelayProperty; + private SerializedProperty m_SpawnTimeOutProperty; private SerializedProperty m_RpcHashSizeProperty; private SerializedProperty m_LoadSceneTimeOutProperty; private SerializedProperty m_PrefabsList; @@ -51,27 +51,6 @@ public class NetworkManagerEditor : UnityEditor.Editor private readonly List m_TransportTypes = new List(); private string[] m_TransportNames = { "Select transport..." }; - /// - public override void OnInspectorGUI() - { - Initialize(); - CheckNullProperties(); - -#if !MULTIPLAYER_TOOLS - DrawInstallMultiplayerToolsTip(); -#endif - - if (m_NetworkManager.IsServer || m_NetworkManager.IsClient) - { - DrawDisconnectButton(); - } - else - { - DrawAllPropertyFields(); - ShowStartConnectionButtons(); - } - } - private void ReloadTransports() { m_TransportTypes.Clear(); @@ -120,15 +99,22 @@ private void Initialize() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); +#if NGO_DAMODE + m_SessionModeProperty = m_NetworkConfigProperty.FindPropertyRelative("SessionMode"); +#endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs"); - m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement"); m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds"); m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay"); - m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); + + m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement"); + m_SpawnTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("SpawnTimeout"); m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); + + + m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty .FindPropertyRelative(nameof(NetworkConfig.Prefabs)) .FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists)); @@ -148,325 +134,217 @@ private void CheckNullProperties() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); +#if NGO_DAMODE + m_SessionModeProperty = m_NetworkConfigProperty.FindPropertyRelative("SessionMode"); +#endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs"); - m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement"); m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds"); m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay"); - m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); + + + m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement"); + m_SpawnTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("SpawnTimeout"); m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); + + m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty .FindPropertyRelative(nameof(NetworkConfig.Prefabs)) .FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists)); } - private void DrawAllPropertyFields() + /// + public override void OnInspectorGUI() { - serializedObject.Update(); - EditorGUILayout.PropertyField(m_RunInBackgroundProperty); - EditorGUILayout.PropertyField(m_LogLevelProperty); - EditorGUILayout.Space(); - - EditorGUILayout.PropertyField(m_PlayerPrefabProperty); - EditorGUILayout.Space(); - - DrawPrefabListField(); - - EditorGUILayout.Space(); - - EditorGUILayout.LabelField("General", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_ProtocolVersionProperty); - - DrawTransportField(); - - EditorGUILayout.PropertyField(m_TickRateProperty); - - EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel); - - EditorGUILayout.PropertyField(m_EnsureNetworkVariableLengthSafetyProperty); - - EditorGUILayout.LabelField("Connection", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_ConnectionApprovalProperty); - - using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.ConnectionApproval)) - { - EditorGUILayout.PropertyField(m_ClientConnectionBufferTimeoutProperty); - } - - EditorGUILayout.LabelField("Spawning", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty); - - EditorGUILayout.PropertyField(m_RecycleNetworkIdsProperty); - - using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.RecycleNetworkIds)) - { - EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty); - } - - EditorGUILayout.LabelField("Bandwidth", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_RpcHashSizeProperty); - - EditorGUILayout.LabelField("Scene Management", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_EnableSceneManagementProperty); - - using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement)) - { - EditorGUILayout.PropertyField(m_LoadSceneTimeOutProperty); - } - - serializedObject.ApplyModifiedProperties(); - } + Initialize(); + CheckNullProperties(); - private void DrawTransportField() - { -#if RELAY_INTEGRATION_AVAILABLE - var useRelay = EditorPrefs.GetBool(k_UseEasyRelayIntegrationKey, false); -#else - var useRelay = false; +#if !MULTIPLAYER_TOOLS + DrawInstallMultiplayerToolsTip(); #endif - if (useRelay) + if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) { - EditorGUILayout.HelpBox("Test connection with relay is enabled, so the default Unity Transport will be used", MessageType.Info); - GUI.enabled = false; + serializedObject.Update(); + EditorGUILayout.PropertyField(m_RunInBackgroundProperty); + EditorGUILayout.PropertyField(m_LogLevelProperty); +#if NGO_DAMODE + EditorGUILayout.PropertyField(m_SessionModeProperty); +#endif + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(m_ProtocolVersionProperty); EditorGUILayout.PropertyField(m_NetworkTransportProperty); - GUI.enabled = true; - return; - } + if (m_NetworkTransportProperty.objectReferenceValue == null) + { + EditorGUILayout.HelpBox("You have no transport selected. A transport is required for netcode to work. Which one do you want?", MessageType.Warning); - EditorGUILayout.PropertyField(m_NetworkTransportProperty); + int selection = EditorGUILayout.Popup(0, m_TransportNames); - if (m_NetworkTransportProperty.objectReferenceValue == null) - { - EditorGUILayout.HelpBox("You have no transport selected. A transport is required for netcode to work. Which one do you want?", MessageType.Warning); + if (selection > 0) + { + ReloadTransports(); - int selection = EditorGUILayout.Popup(0, m_TransportNames); + var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]); + m_NetworkTransportProperty.objectReferenceValue = transportComponent; - if (selection > 0) + Repaint(); + } + } + EditorGUILayout.PropertyField(m_TickRateProperty); + EditorGUILayout.PropertyField(m_SpawnTimeOutProperty); + EditorGUILayout.PropertyField(m_ConnectionApprovalProperty); + if (m_NetworkManager.NetworkConfig.ConnectionApproval) { - ReloadTransports(); - - var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]); - m_NetworkTransportProperty.objectReferenceValue = transportComponent; - - Repaint(); + EditorGUILayout.PropertyField(m_ClientConnectionBufferTimeoutProperty); } - } - } - -#if RELAY_INTEGRATION_AVAILABLE - private readonly string k_UseEasyRelayIntegrationKey = "NetworkManagerUI_UseRelay_" + Application.dataPath.GetHashCode(); - private string m_JoinCode = ""; - private string m_StartConnectionError = null; - private string m_Region = ""; - - // wait for next frame so that ImGui finishes the current frame - private static void RunNextFrame(Action action) => EditorApplication.delayCall += () => action(); -#endif - - private void ShowStartConnectionButtons() - { - EditorGUILayout.LabelField("Start Connection", EditorStyles.boldLabel); - -#if RELAY_INTEGRATION_AVAILABLE - // use editor prefs to persist the setting when entering / leaving play mode / exiting Unity - var useRelay = EditorPrefs.GetBool(k_UseEasyRelayIntegrationKey, false); - GUILayout.BeginHorizontal(); - useRelay = GUILayout.Toggle(useRelay, "Try Relay in the Editor"); - - var icon = EditorGUIUtility.IconContent("_Help"); - icon.tooltip = "This will help you test relay in the Editor. Click here to know how to integrate Relay in your build"; - if (GUILayout.Button(icon, GUIStyle.none, GUILayout.Width(20))) - { - Application.OpenURL("https://docs-multiplayer.unity3d.com/netcode/current/relay/"); - } - GUILayout.EndHorizontal(); - - EditorPrefs.SetBool(k_UseEasyRelayIntegrationKey, useRelay); - if (useRelay && !Application.isPlaying && !CloudProjectSettings.projectBound) - { - EditorGUILayout.HelpBox("To use relay, you need to setup your project in the Project Settings in the Services section.", MessageType.Warning); - if (GUILayout.Button("Open Project settings")) + EditorGUILayout.PropertyField(m_EnsureNetworkVariableLengthSafetyProperty, new GUIContent("NetworkVariable Length Safety")); + EditorGUILayout.PropertyField(m_RecycleNetworkIdsProperty); + if (m_NetworkManager.NetworkConfig.RecycleNetworkIds) { - SettingsService.OpenProjectSettings("Project/Services"); + EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty); } - } -#else - var useRelay = false; -#endif - - string buttonDisabledReasonSuffix = ""; - - if (!EditorApplication.isPlaying) - { - buttonDisabledReasonSuffix = ". This can only be done in play mode"; - GUI.enabled = false; - } - - if (useRelay) - { - ShowStartConnectionButtons_Relay(buttonDisabledReasonSuffix); - } - else - { - ShowStartConnectionButtons_Standard(buttonDisabledReasonSuffix); - } + EditorGUILayout.PropertyField(m_RpcHashSizeProperty); - if (!EditorApplication.isPlaying) - { - GUI.enabled = true; - } - } + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty); + EditorGUILayout.PropertyField(m_PlayerPrefabProperty, new GUIContent("Default Player Prefab")); - private void ShowStartConnectionButtons_Relay(string buttonDisabledReasonSuffix) - { -#if RELAY_INTEGRATION_AVAILABLE - - void AddStartServerOrHostButton(bool isServer) - { - var type = isServer ? "Server" : "Host"; - if (GUILayout.Button(new GUIContent($"Start {type}", $"Starts a {type} instance with Relay{buttonDisabledReasonSuffix}"))) + if (m_NetworkManager.NetworkConfig.HasOldPrefabList()) { - m_StartConnectionError = null; - RunNextFrame(async () => + EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info); + if (GUILayout.Button(new GUIContent("Migrate Prefab List", "Converts the old format Network Prefab list to a new Scriptable Object"))) { - try + // Default directory + var directory = "Assets/"; + var assetPath = AssetDatabase.GetAssetPath(m_NetworkManager); + if (assetPath == "") + { + assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(m_NetworkManager); + } + + if (assetPath != "") { - var (joinCode, allocation) = isServer ? await m_NetworkManager.StartServerWithRelay() : await m_NetworkManager.StartHostWithRelay(); - m_JoinCode = joinCode; - m_Region = allocation.Region; - Repaint(); + directory = Path.GetDirectoryName(assetPath); } - catch (Exception e) + else { - m_StartConnectionError = e.Message; - throw; +#if UNITY_2021_1_OR_NEWER + var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject); +#else + var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject); +#endif + if (prefabStage != null) + { + var prefabPath = prefabStage.assetPath; + if (!string.IsNullOrEmpty(prefabPath)) + { + directory = Path.GetDirectoryName(prefabPath); + } + } + if (m_NetworkManager.gameObject.scene != null) + { + var scenePath = m_NetworkManager.gameObject.scene.path; + if (!string.IsNullOrEmpty(scenePath)) + { + directory = Path.GetDirectoryName(scenePath); + } + } } - }); + var networkPrefabs = m_NetworkManager.NetworkConfig.MigrateOldNetworkPrefabsToNetworkPrefabsList(); + string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset"); + Debug.Log("Saving migrated Network Prefabs List to " + path); + AssetDatabase.CreateAsset(networkPrefabs, path); + EditorUtility.SetDirty(m_NetworkManager); + } + } + else + { + if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Count == 0) + { + EditorGUILayout.HelpBox("You have no prefab list selected. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning); + } + EditorGUILayout.PropertyField(m_PrefabsList); } - } - AddStartServerOrHostButton(isServer: true); - AddStartServerOrHostButton(isServer: false); + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Scene Management Settings", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(m_EnableSceneManagementProperty); + if (m_NetworkManager.NetworkConfig.EnableSceneManagement) + { + EditorGUILayout.PropertyField(m_LoadSceneTimeOutProperty); + } - GUILayout.Space(8f); - m_JoinCode = EditorGUILayout.TextField("Relay Join Code", m_JoinCode); - if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance with Relay" + buttonDisabledReasonSuffix))) - { - m_StartConnectionError = null; - RunNextFrame(async () => + serializedObject.ApplyModifiedProperties(); + + + // Start buttons below { - if (string.IsNullOrEmpty(m_JoinCode)) + string buttonDisabledReasonSuffix = ""; + + if (!EditorApplication.isPlaying) { - m_StartConnectionError = "Please provide a join code!"; - return; + buttonDisabledReasonSuffix = ". This can only be done in play mode"; + GUI.enabled = false; } - try + if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix))) { - var allocation = await m_NetworkManager.StartClientWithRelay(m_JoinCode); - m_Region = allocation.Region; - Repaint(); + m_NetworkManager.StartHost(); } - catch (Exception e) + + if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix))) { - m_StartConnectionError = e.Message; - throw; + m_NetworkManager.StartServer(); } - }); - } - - if (Application.isPlaying && !string.IsNullOrEmpty(m_StartConnectionError)) - { - EditorGUILayout.HelpBox(m_StartConnectionError, MessageType.Error); - } -#endif - } - - private void ShowStartConnectionButtons_Standard(string buttonDisabledReasonSuffix) - { - if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartHost(); - } - - if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartServer(); - } - if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartClient(); - } - } - - private void DrawDisconnectButton() - { - string instanceType = string.Empty; + if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix))) + { + m_NetworkManager.StartClient(); + } - if (m_NetworkManager.IsHost) - { - instanceType = "Host"; - } - else if (m_NetworkManager.IsServer) - { - instanceType = "Server"; + if (!EditorApplication.isPlaying) + { + GUI.enabled = true; + } + } } - else if (m_NetworkManager.IsClient) + else { - instanceType = "Client"; - } + string instanceType = string.Empty; - EditorGUILayout.HelpBox($"You cannot edit the NetworkConfig when a {instanceType} is running.", MessageType.Info); - -#if RELAY_INTEGRATION_AVAILABLE - if (!string.IsNullOrEmpty(m_JoinCode) && !string.IsNullOrEmpty(m_Region)) - { - var style = new GUIStyle(EditorStyles.helpBox) + if (m_NetworkManager.IsHost) { - fontSize = 10, - alignment = TextAnchor.MiddleCenter, - }; - - GUILayout.BeginHorizontal(style, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false), GUILayout.MaxWidth(800)); - GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent(k_InfoIconName)), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true)); - GUILayout.Space(25f); - GUILayout.BeginVertical(); - GUILayout.Space(4f); - GUILayout.Label($"Connected via relay ({m_Region}).\nJoin code: {m_JoinCode}", EditorStyles.miniLabel, GUILayout.ExpandHeight(true)); - - if (GUILayout.Button("Copy code", GUILayout.ExpandHeight(true))) + instanceType = "Host"; + } + else if (m_NetworkManager.IsServer) + { + instanceType = "Server"; + } + else if (m_NetworkManager.IsClient) { - GUIUtility.systemCopyBuffer = m_JoinCode; + instanceType = "Client"; } - GUILayout.Space(4f); - GUILayout.EndVertical(); - GUILayout.Space(2f); - GUILayout.EndHorizontal(); - } -#endif + EditorGUILayout.HelpBox("You cannot edit the NetworkConfig when a " + instanceType + " is running.", MessageType.Info); - if (GUILayout.Button(new GUIContent($"Stop {instanceType}", $"Stops the {instanceType} instance."))) - { -#if RELAY_INTEGRATION_AVAILABLE - m_JoinCode = ""; -#endif - m_NetworkManager.Shutdown(); + if (GUILayout.Button(new GUIContent("Stop " + instanceType, "Stops the " + instanceType + " instance."))) + { + m_NetworkManager.Shutdown(); + } } } - private const string k_InfoIconName = "console.infoicon"; private static void DrawInstallMultiplayerToolsTip() { const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager."; const string openDocsButtonText = "Open Docs"; const string dismissButtonText = "Dismiss"; const string targetUrl = "https://docs-multiplayer.unity3d.com/tools/current/install-tools"; - + const string infoIconName = "console.infoicon"; if (NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() != 0) { @@ -497,7 +375,7 @@ private static void DrawInstallMultiplayerToolsTip() GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(s_HelpBoxStyle, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false), GUILayout.MaxWidth(800)); { - GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent(k_InfoIconName)), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true)); + GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent(infoIconName)), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true)); GUILayout.Space(4); GUILayout.Label(getToolsText, s_CenteredWordWrappedLabelStyle, GUILayout.ExpandHeight(true)); @@ -530,69 +408,5 @@ private static void DrawInstallMultiplayerToolsTip() GUILayout.Space(10); } - - private void DrawPrefabListField() - { - if (!m_NetworkManager.NetworkConfig.HasOldPrefabList()) - { - if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Count == 0) - { - EditorGUILayout.HelpBox("You have no prefab list selected. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning); - } - - EditorGUILayout.PropertyField(m_PrefabsList); - return; - } - - // Old format of prefab list - EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info); - if (GUILayout.Button(new GUIContent("Migrate Prefab List", "Converts the old format Network Prefab list to a new Scriptable Object"))) - { - // Default directory - var directory = "Assets/"; - var assetPath = AssetDatabase.GetAssetPath(m_NetworkManager); - if (assetPath == "") - { - assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(m_NetworkManager); - } - - if (assetPath != "") - { - directory = Path.GetDirectoryName(assetPath); - } - else - { - -#if UNITY_2021_1_OR_NEWER - var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject); -#else - var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject); -#endif - if (prefabStage != null) - { - var prefabPath = prefabStage.assetPath; - if (!string.IsNullOrEmpty(prefabPath)) - { - directory = Path.GetDirectoryName(prefabPath); - } - } - - if (m_NetworkManager.gameObject.scene != null) - { - var scenePath = m_NetworkManager.gameObject.scene.path; - if (!string.IsNullOrEmpty(scenePath)) - { - directory = Path.GetDirectoryName(scenePath); - } - } - } - - var networkPrefabs = m_NetworkManager.NetworkConfig.MigrateOldNetworkPrefabsToNetworkPrefabsList(); - string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset"); - Debug.Log("Saving migrated Network Prefabs List to " + path); - AssetDatabase.CreateAsset(networkPrefabs, path); - EditorUtility.SetDirty(m_NetworkManager); - } - } } } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 1b582f88e2..177e662014 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using UnityEditor; using UnityEngine; @@ -49,6 +50,12 @@ public override void OnInspectorGUI() { var guiEnabled = GUI.enabled; GUI.enabled = false; +#if NGO_DAMODE + if (m_NetworkObject.NetworkManager.DistributedAuthorityMode) + { + EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(NetworkObject.Ownership))); + } +#endif EditorGUILayout.TextField(nameof(NetworkObject.GlobalObjectIdHash), m_NetworkObject.GlobalObjectIdHash.ToString()); EditorGUILayout.TextField(nameof(NetworkObject.NetworkObjectId), m_NetworkObject.NetworkObjectId.ToString()); EditorGUILayout.TextField(nameof(NetworkObject.OwnerClientId), m_NetworkObject.OwnerClientId.ToString()); @@ -138,4 +145,50 @@ private void OnDestroy() NetworkBehaviourEditor.CheckForNetworkObject(m_GameObject, true); } } + +#if NGO_DAMODE + [CustomPropertyDrawer(typeof(NetworkObject.OwnershipStatus))] + public class NetworkObjectOwnership : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + label = EditorGUI.BeginProperty(position, label, property); + // Don't allow modification while in play mode + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); + + // This is a temporary work around due to EditorGUI.EnumFlagsField having a bug in how it displays mask values. + // For now, we will just display the flags as a toggle and handle the masking of the value ourselves. + EditorGUILayout.BeginHorizontal(); + var names = System.Enum.GetNames(typeof(NetworkObject.OwnershipStatus)).ToList(); + names.RemoveAt(0); + var value = property.enumValueFlag; + var compareValue = 0x01; + GUILayout.Label(label); + foreach (var name in names) + { + var isSet = (value & compareValue) > 0; + isSet = GUILayout.Toggle(isSet, name); + if (isSet) + { + value |= compareValue; + } + else + { + value &= ~compareValue; + } + compareValue = compareValue << 1; + } + property.enumValueFlag = value; + EditorGUILayout.EndHorizontal(); + + // The below can cause visual anomalies and/or throws an exception within the EditorGUI itself (index out of bounds of the array). and has + // The visual anomaly is when you select one field it is set in the drop down but then the flags selection in the popup menu selects more items + // even though if you exit the popup menu the flag setting is correct. + //var ownership = (NetworkObject.OwnershipStatus)EditorGUI.EnumFlagsField(position, label, (NetworkObject.OwnershipStatus)property.enumValueFlag); + //property.enumValueFlag = (int)ownership; + EditorGUI.EndDisabledGroup(); + EditorGUI.EndProperty(); + } + } +#endif } diff --git a/com.unity.netcode.gameobjects/LICENSE.md b/com.unity.netcode.gameobjects/LICENSE.md index a5eb171398..031978c204 100644 --- a/com.unity.netcode.gameobjects/LICENSE.md +++ b/com.unity.netcode.gameobjects/LICENSE.md @@ -1,9 +1,7 @@ -MIT License +Unity Companion License (UCL License) -Copyright (c) 2021 Unity Technologies +com.unity.netcode.gameobjects copyright © 2021-2024 Unity Technologies +Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license). +Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 9b855ec5ba..92f49495fa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -129,9 +129,9 @@ public class NetworkConfig public int LoadSceneTimeOut = 120; /// - /// The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped. + /// The amount of time a message will be held (deferred) if the destination NetworkObject needed to process the message doesn't exist yet. If the NetworkObject is not spawned within this time period, all deferred messages for that NetworkObject will be dropped. /// - [Tooltip("The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped")] + [Tooltip("The amount of time a message will be held (deferred) if the destination NetworkObject needed to process the message doesn't exist yet. If the NetworkObject is not spawned within this time period, all deferred messages for that NetworkObject will be dropped.")] public float SpawnTimeout = 10f; /// @@ -149,6 +149,19 @@ public class NetworkConfig /// public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets) + +#if NGO_DAMODE + + [Tooltip("Determines if the network session will run in clinet-server or distributed authority mode.")] + public SessionModeTypes SessionMode; + + [HideInInspector] + public bool UseCMBService; + + [Tooltip("When enabled (default), the player prefab will automatically be spawned (client-side) upon the client being approved and synchronized.")] + public bool AutoSpawnPlayerPrefabClientSide = true; +#endif + /// /// Returns a base64 encoded version of the configuration /// diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs index 370f651a8b..ba7be5e309 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs @@ -1,7 +1,17 @@ using System.Collections.Generic; +using UnityEngine; namespace Unity.Netcode { + +#if NGO_DAMODE + public enum SessionModeTypes + { + ClientServer, + DistributedAuthority + } +#endif + /// /// A NetworkClient /// @@ -33,6 +43,17 @@ public class NetworkClient /// internal bool IsApproved { get; set; } +#if NGO_DAMODE + public SessionModeTypes SessionModeType { get; internal set; } + + public bool DAHost { get; internal set; } + + /// + /// Is true when the client has been assigned session ownership in distributed authority mode + /// + public bool IsSessionOwner { get; internal set; } +#endif + /// /// The ClientId of the NetworkClient /// @@ -50,21 +71,58 @@ public class NetworkClient internal NetworkSpawnManager SpawnManager { get; private set; } - internal void SetRole(bool isServer, bool isClient, NetworkManager networkManager = null) + internal bool SetRole(bool isServer, bool isClient, NetworkManager networkManager = null) { + ResetClient(isServer, isClient); + IsServer = isServer; IsClient = isClient; - if (!IsServer && !isClient) + + if (networkManager != null) + { + SpawnManager = networkManager.SpawnManager; +#if NGO_DAMODE + SessionModeType = networkManager.NetworkConfig.SessionMode; + + if (SessionModeType == SessionModeTypes.DistributedAuthority) + { + DAHost = IsClient && IsServer; + + // DANGO-TODO: We might allow a dedicated mock CMB server, but for now do not allow this + if (!IsClient && IsServer) + { + Debug.LogError("You cannot start NetworkManager as a server when operating in distributed authority mode!"); + return false; + } + + if (DAHost && networkManager.CMBServiceConnection) + { + Debug.LogError("You cannot start a host when connecting to a distributed authority CMB Service!"); + return false; + } + } +#endif + } + return true; + } + + /// + /// Only to be invoked when setting the role. + /// This resets the current NetworkClient's properties. + /// + private void ResetClient(bool isServer, bool isClient) + { + // If we are niether client nor server, then reset properties (i.e. client has no role) + if (!IsServer && !IsClient) { PlayerObject = null; ClientId = 0; IsConnected = false; IsApproved = false; - } - - if (networkManager != null) - { - SpawnManager = networkManager.SpawnManager; + SpawnManager = null; +#if NGO_DAMODE + DAHost = false; +#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index d73759874a..2a64160522 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1,12 +1,15 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Profiling; using UnityEngine; +#if !NGO_DAMODE using Object = UnityEngine.Object; +#endif namespace Unity.Netcode { @@ -366,6 +369,10 @@ internal void PollAndHandleNetworkEvents() { networkEvent = NetworkManager.NetworkConfig.NetworkTransport.PollEvent(out ulong transportClientId, out ArraySegment payload, out float receiveTime); HandleNetworkEvent(networkEvent, transportClientId, payload, receiveTime); + if (networkEvent == NetworkEvent.Disconnect || networkEvent == NetworkEvent.TransportFailure) + { + break; + } // Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum) } while (NetworkManager.IsListening && networkEvent != NetworkEvent.Nothing); @@ -430,16 +437,21 @@ internal void ConnectEventHandler(ulong transportClientId) { if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { - NetworkLog.LogInfo("Client Connected"); + var hostServer = NetworkManager.IsHost ? "Host" : "Server"; + NetworkLog.LogInfo($"[{hostServer}-Side] Transport connection established with pending Client-{clientId}."); } - AddPendingClient(clientId); } else { if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { - NetworkLog.LogInfo("Connected"); +#if NGO_DAMODE + var serverOrService = NetworkManager.DistributedAuthorityMode ? NetworkManager.CMBServiceConnection ? "service" : "DAHost" : "server"; + NetworkLog.LogInfo($"[Approval Pending][Client] Transport connection with {serverOrService} established! Awaiting connection approval..."); +#else + NetworkLog.LogInfo("[Pending Client] Transport connection with server established! Awaiting connection approval..."); +#endif } SendConnectionRequest(); @@ -546,6 +558,11 @@ private void SendConnectionRequest() { var message = new ConnectionRequestMessage { +#if NGO_DAMODE + CMBServiceConnection = NetworkManager.CMBServiceConnection, + TickRate = NetworkManager.NetworkConfig.TickRate, + EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement, +#endif // Since only a remote client will send a connection request, we should always force the rebuilding of the NetworkConfig hash value ConfigHash = NetworkManager.NetworkConfig.GetConfig(false), ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval, @@ -701,6 +718,12 @@ internal void ProcessPendingApprovals() } } + /// + /// Adding this because message hooks cannot happen fast enough under certain scenarios + /// where the message is sent and responded to before the hook is in place. + /// + internal bool MockSkippingApproval; + /// /// Server Side: Handles the approval of a client /// @@ -712,12 +735,19 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne LocalClient.IsApproved = response.Approved; if (response.Approved) { + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"[Server-Side] Pending Client-{ownerClientId} connection approved!"); + } // The client was approved, stop the server-side approval time out coroutine RemovePendingClient(ownerClientId); var client = AddClient(ownerClientId); - - if (response.CreatePlayerObject) +#if NGO_DAMODE + if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && NetworkManager.NetworkConfig.PlayerPrefab != null) +#else + if (response.CreatePlayerObject && NetworkManager.NetworkConfig.PlayerPrefab != null) +#endif { var prefabNetworkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash; @@ -732,6 +762,9 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne HasTransform = prefabNetworkObject.SynchronizeTransform, Hash = playerPrefabHash, TargetClientId = ownerClientId, +#if NGO_DAMODE + DontDestroyWithOwner = prefabNetworkObject.DontDestroyWithOwner, +#endif Transform = new NetworkObject.SceneObject.TransformData { Position = response.Position.GetValueOrDefault(), @@ -761,6 +794,9 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { OwnerClientId = ownerClientId, NetworkTick = NetworkManager.LocalTime.Tick, +#if NGO_DAMODE + IsDistributedAuthority = NetworkManager.DistributedAuthorityMode, +#endif ConnectedClientIds = new NativeArray(ConnectedClientIds.Count, Allocator.Temp) }; @@ -794,10 +830,20 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne }; } } - - SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); + if (!MockSkippingApproval) + { + SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); + } + else + { + NetworkLog.LogInfo("Mocking server not responding with connection approved..."); + } message.MessageVersions.Dispose(); message.ConnectedClientIds.Dispose(); + if (MockSkippingApproval) + { + return; + } // If scene management is disabled, then we are done and notify the local host-server the client is connected if (!NetworkManager.NetworkConfig.EnableSceneManagement) @@ -808,10 +854,23 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { InvokeOnPeerConnectedCallback(ownerClientId); } +#if NGO_DAMODE + NetworkManager.SpawnManager.DistributeNetworkObjects(ownerClientId); +#endif + } else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization { - NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) + { + NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId); + } + else if (!NetworkManager.DistributedAuthorityMode) +#endif + { + NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId); + } } } else // Server just adds itself as an observer to all spawned NetworkObjects @@ -819,6 +878,19 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne LocalClient = client; NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId); LocalClient.IsConnected = true; +#if NGO_DAMODE + // If running mock service, then set the instance as the default session owner + if (NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) + { + NetworkManager.SetSessionOwner(NetworkManager.LocalClientId); + NetworkManager.SceneManager.InitializeScenesLoaded(); + } + + if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide) + { + CreateAndSpawnPlayer(ownerClientId); + } +#endif } if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkManager.NetworkConfig.PlayerPrefab == null)) @@ -826,6 +898,13 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne return; } +#if NGO_DAMODE + // Players are always spawned by their respective client, exit early. (DAHost mode anyway, CMB Service will never spawn player prefab) + if (NetworkManager.DistributedAuthorityMode) + { + return; + } +#endif // Separating this into a contained function call for potential further future separation of when this notification is sent. ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash); } @@ -840,11 +919,30 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId); MessageManager.ProcessSendQueues(); } - DisconnectRemoteClient(ownerClientId); } } +#if NGO_DAMODE + /// + /// Client-Side Spawning in distributed authority mode uses this to spawn the player. + /// + internal void CreateAndSpawnPlayer(ulong ownerId, Vector3 position = default, Quaternion rotation = default) + { + if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide) + { + var playerPrefab = NetworkManager.FetchLocalPlayerPrefabToSpawn(); + if (playerPrefab != null) + { + var globalObjectIdHash = playerPrefab.GetComponent().GlobalObjectIdHash; + var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, position, rotation); + networkObject.IsSceneObject = false; + networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene); + } + } + } +#endif + /// /// Spawns the newly approved player /// @@ -864,7 +962,10 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) var message = new CreateObjectMessage { - ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key) + ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key), +#if NGO_DAMODE + IncludesSerializedObject = true, +#endif }; message.ObjectInfo.Hash = playerPrefabHash; message.ObjectInfo.IsSceneObject = false; @@ -883,20 +984,101 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) /// internal NetworkClient AddClient(ulong clientId) { + if (ConnectedClients.ContainsKey(clientId) && ConnectedClientIds.Contains(clientId) && ConnectedClientsList.Contains(ConnectedClients[clientId])) + { + return ConnectedClients[clientId]; + } + var networkClient = LocalClient; - networkClient = new NetworkClient(); + // If this is not the local client then create a new one + if (clientId != NetworkManager.LocalClientId) + { + networkClient = new NetworkClient(); + } networkClient.SetRole(clientId == NetworkManager.ServerClientId, isClient: true, NetworkManager); networkClient.ClientId = clientId; + if (!ConnectedClients.ContainsKey(clientId)) + { + ConnectedClients.Add(clientId, networkClient); + } + if (!ConnectedClientsList.Contains(networkClient)) + { + ConnectedClientsList.Add(networkClient); + } + +#if NGO_DAMODE + if (NetworkManager.LocalClientId != clientId) + { + if ((!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer) || + (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && NetworkManager.DAHost && NetworkManager.LocalClient.IsSessionOwner)) + { + var message = new ClientConnectedMessage { ClientId = clientId }; + NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, ConnectedClientIds.Where((c) => c != NetworkManager.LocalClientId).ToArray()); + } + else if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && NetworkManager.DAHost && !NetworkManager.LocalClient.IsSessionOwner) + { + var message = new ClientConnectedMessage + { + ShouldSynchronize = true, + ClientId = clientId + }; + NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.CurrentSessionOwner); + } + } + if (!ConnectedClientIds.Contains(clientId)) + { + ConnectedClientIds.Add(clientId); + } - ConnectedClients.Add(clientId, networkClient); - ConnectedClientsList.Add(networkClient); + foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) + { + if (networkObject.SpawnWithObservers) + { + networkObject.Observers.Add(clientId); + } + } +#else var message = new ClientConnectedMessage { ClientId = clientId }; NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); ConnectedClientIds.Add(clientId); +#endif + return networkClient; } +#if NGO_DAMODE + + /// + /// Invoked on clients when another client disconnects + /// + /// the client identifier to remove + internal void RemoveClient(ulong clientId) + { + if (ConnectedClientIds.Contains(clientId)) + { + ConnectedClientIds.Remove(clientId); + } + if (ConnectedClients.ContainsKey(clientId)) + { + ConnectedClientsList.Remove(ConnectedClients[clientId]); + } + + ConnectedClients.Remove(clientId); + + foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) + { + networkObject.Observers.Remove(clientId); + } + } +#endif + + /// + /// DANGO-TODO: Until we have the CMB Server end-to-end with all features verified working via integration tests, + /// I am keeping this debug toggle available. (NSS) + /// + internal bool EnableDistributeLogging; + /// /// Server-Side: /// Invoked when a client is disconnected from a server-host @@ -923,25 +1105,63 @@ internal void OnClientDisconnectFromServer(ulong clientId) { if (!playerObject.DontDestroyWithOwner) { - if (NetworkManager.PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash)) +#if NGO_DAMODE + // DANGO-TODO: This is something that would be best for CMB Service to handle as it is part of the disconnection process + // 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.DistributedAuthorityMode && 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 != null) + { + // 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(); + } + } + } +#endif + + if (NetworkManager.PrefabHandler.ContainsHandler(playerObject.GlobalObjectIdHash)) { - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject); +#if NGO_DAMODE + if (NetworkManager.DAHost && NetworkManager.DistributedAuthorityMode) + { + NetworkManager.SpawnManager.DespawnObject(playerObject, true, NetworkManager.DistributedAuthorityMode); + } + else +#endif + { + NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(playerObject); + } } else 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. +#if NGO_DAMODE + NetworkManager.SpawnManager.DespawnObject(playerObject, true, NetworkManager.DistributedAuthorityMode); +#else NetworkManager.SpawnManager.DespawnObject(playerObject, true); +#endif } } else if (!NetworkManager.ShutdownInProgress) { - playerObject.RemoveOwnership(); + if (!NetworkManager.ShutdownInProgress) + { + playerObject.RemoveOwnership(); + } } } // Get the NetworkObjects owned by the disconnected client - var clientOwnedObjects = NetworkManager.SpawnManager.GetClientOwnedObjects(clientId); + var clientOwnedObjects = NetworkManager.SpawnManager.SpawnedObjectsList.Where((c) => c.OwnerClientId == clientId).ToList(); if (clientOwnedObjects == null) { // This could happen if a client is never assigned a player object and is disconnected @@ -954,6 +1174,11 @@ internal void OnClientDisconnectFromServer(ulong clientId) else { // Handle changing ownership and prefab handlers +#if NGO_DAMODE + var clientCounter = 0; + var predictedClientCount = ConnectedClientsList.Count - 1; + var remainingClients = NetworkManager.DistributedAuthorityMode ? ConnectedClientsList.Where((c) => c.ClientId != clientId).ToList() : null; +#endif for (int i = clientOwnedObjects.Count - 1; i >= 0; i--) { var ownedObject = clientOwnedObjects[i]; @@ -963,16 +1188,80 @@ internal void OnClientDisconnectFromServer(ulong clientId) { if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash)) { +#if NGO_DAMODE + NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); +#endif NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]); } else { +#if NGO_DAMODE + NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); +#else Object.Destroy(ownedObject.gameObject); +#endif } } else if (!NetworkManager.ShutdownInProgress) { - ownedObject.RemoveOwnership(); +#if NGO_DAMODE + // NOTE: All of the below code only handles ownership transfer. + // For client-server, we just remove the ownership. + // For distributed authority, we need to change ownership based on parenting + if (NetworkManager.DistributedAuthorityMode) + { + // Only NetworkObjects that have the OwnershipStatus.Distributable flag set and no parent + // (ownership is transferred to all children) will have their ownership redistributed. + if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null) + { + if (ownedObject.IsOwnershipLocked) + { + ownedObject.SetOwnershipLock(false); + } + + // DANGO-TODO: We will want to match how the CMB service handles this. For now, we just try to evenly distribute + // ownership. + var targetOwner = NetworkManager.ServerClientId; + if (predictedClientCount > 1) + { + clientCounter++; + clientCounter = clientCounter % predictedClientCount; + targetOwner = remainingClients[clientCounter].ClientId; + } + if (EnableDistributeLogging) + { + Debug.Log($"[Disconnected][Client-{clientId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); + } + NetworkManager.SpawnManager.ChangeOwnership(ownedObject, targetOwner, true); + // DANGO-TODO: Should we try handling inactive NetworkObjects? + // Ownership gets passed down to all children + var childNetworkObjects = ownedObject.GetComponentsInChildren(); + foreach (var childObject in childNetworkObjects) + { + // We already changed ownership for this + if (childObject == ownedObject) + { + continue; + } + // If the client owner disconnected, it is ok to unlock this at this point in time. + if (childObject.IsOwnershipLocked) + { + childObject.SetOwnershipLock(false); + } + + NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true); + if (EnableDistributeLogging) + { + Debug.Log($"[Disconnected][Client-{clientId}][Child of {ownedObject.NetworkObjectId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); + } + } + } + } + else +#endif + { + ownedObject.RemoveOwnership(); + } } } } @@ -993,6 +1282,39 @@ internal void OnClientDisconnectFromServer(ulong clientId) ConnectedClientIds.Remove(clientId); var message = new ClientDisconnectedMessage { ClientId = clientId }; MessageManager?.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); + +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode && !NetworkManager.ShutdownInProgress && NetworkManager.IsListening) + { + var newSessionOwner = NetworkManager.LocalClientId; + if (ConnectedClientIds.Count > 1) + { + var lowestRTT = ulong.MaxValue; + var unityTransport = NetworkManager.NetworkConfig.NetworkTransport as Transports.UTP.UnityTransport; + + foreach (var identifier in ConnectedClientIds) + { + if (identifier == NetworkManager.LocalClientId) + { + continue; + } + var rtt = unityTransport.GetCurrentRtt(identifier); + if (rtt < lowestRTT) + { + newSessionOwner = identifier; + lowestRTT = rtt; + } + } + } + + var sessionOwnerMessage = new SessionOwnerMessage() + { + SessionOwner = newSessionOwner, + }; + MessageManager?.SendMessage(ref sessionOwnerMessage, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); + NetworkManager.SetSessionOwner(newSessionOwner); + } +#endif } // If the client ID transport map exists @@ -1259,8 +1581,8 @@ internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in N internal int SendMessage(ref T message, NetworkDelivery delivery, ulong clientId) where T : INetworkMessage { - // Prevent server sending to itself - if (LocalClient.IsServer && clientId == NetworkManager.ServerClientId) + // Prevent server sending to itself or if there is no MessageManager yet then exit early + if ((LocalClient.IsServer && clientId == NetworkManager.ServerClientId) || MessageManager == null) { return 0; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 39a7622fc7..c2c4ce4f47 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +#if NGO_DAMODE +using System.Linq; +#endif using Unity.Collections; using UnityEngine; @@ -69,6 +72,7 @@ internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery) #pragma warning restore IDE1006 // restore naming rule violation check { + var networkManager = NetworkManager; var serverRpcMessage = new ServerRpcMessage { Metadata = new RpcMetadata @@ -88,7 +92,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; break; case RpcDelivery.Unreliable: - if (bufferWriter.Length > NetworkManager.MessageManager.NonFragmentedMessageMaxSize) + if (bufferWriter.Length > networkManager.MessageManager.NonFragmentedMessageMaxSize) { throw new OverflowException("RPC parameters are too large for unreliable delivery."); } @@ -97,16 +101,21 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } var rpcWriteSize = 0; - - // If we are a server/host then we just no op and send to ourself - if (IsHost || IsServer) + // Authority just no ops and sends to itself + // Client-Server: Only the server-host sends to self + // DA Mode: Only the owner sends to self +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode && IsOwner || !networkManager.DistributedAuthorityMode && IsServer) +#else + if (IsServer) +#endif { using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp); var context = new NetworkContext { SenderId = NetworkManager.ServerClientId, - Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup, - SystemOwner = NetworkManager, + Timestamp = networkManager.RealTimeProvider.RealTimeSinceStartup, + SystemOwner = networkManager, // header information isn't valid since it's not a real message. // RpcMessage doesn't access this stuff so it's just left empty. Header = new NetworkMessageHeader(), @@ -119,7 +128,34 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } else { +#if NGO_DAMODE + // If not connected to the CMB state service + if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) + { + // DAHost sends directly to the owner + if (networkManager.DAHost || !networkManager.DAHost && NetworkManager.ServerClientId == OwnerClientId) + { + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref serverRpcMessage, networkDelivery, OwnerClientId); + } + else + { + // Clients forward the message to the DAHost who forwards it to the owner + var forwardServerRpc = new ForwardServerRpcMessage() + { + OwnerId = OwnerClientId, + NetworkDelivery = networkDelivery, + ServerRpcMessage = serverRpcMessage, + }; + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardServerRpc, networkDelivery, NetworkManager.ServerClientId); + } + } + else // Client-Server or CMB Service mode: Send it to the server or service (service forwards to the owner) + { + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId); + } +#else rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId); +#endif } bufferWriter.Dispose(); @@ -149,6 +185,16 @@ internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery) #pragma warning restore IDE1006 // restore naming rule violation check { + var networkManager = NetworkManager; +#if NGO_DAMODE + // When in distributed authority mode, owners are only allowed to send to clients (unless in mock mode with a DAHost) + if (networkManager.DistributedAuthorityMode && networkManager.LocalClientId != OwnerClientId && !networkManager.DAHost) + { + NetworkLog.LogError($"Client-{networkManager.LocalClientId} Does not currently own {gameObject.name} (Current Owner: {OwnerClientId})! In distributed authority mode, only the owners of {nameof(NetworkObject)}s can send ClientRpc messages!"); + bufferWriter.Dispose(); + return; + } +#endif var clientRpcMessage = new ClientRpcMessage { Metadata = new RpcMetadata @@ -168,7 +214,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; break; case RpcDelivery.Unreliable: - if (bufferWriter.Length > NetworkManager.MessageManager.NonFragmentedMessageMaxSize) + if (bufferWriter.Length > networkManager.MessageManager.NonFragmentedMessageMaxSize) { throw new OverflowException("RPC parameters are too large for unreliable delivery."); } @@ -180,69 +226,216 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth // We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC // to ourself. Sadly we have to figure that out from the list of clientIds :( - bool shouldSendToHost = false; + bool shouldInvokeLocally = false; if (clientRpcParams.Send.TargetClientIds != null) { - foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) { - if (targetClientId == NetworkManager.ServerClientId) + foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) { - shouldSendToHost = true; - break; + if ((networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIds.Contains(OwnerClientId)) + || (!networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIds.Contains(NetworkManager.ServerClientId))) + { + shouldInvokeLocally = true; + continue; + } + // Check to make sure we are sending to only observers, if not log an error. + if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) + { + NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); + } } - - // Check to make sure we are sending to only observers, if not log an error. - if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) + } + else +#endif + { + foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) { - NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); + if (targetClientId == NetworkManager.ServerClientId) + { + shouldInvokeLocally = true; + continue; + } + // Check to make sure we are sending to only observers, if not log an error. + if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) + { + NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); + } } } +#if NGO_DAMODE + // For now, if running as the DAHost or in client-server mode, then send the message to the targeted client identifiers + if (networkManager.DAHost || !networkManager.DistributedAuthorityMode && networkManager.IsServer) + { + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); + } + else + { + // Clients sending to other clients when in mock DAHost mode need to have the DAHost forward the message via ForwardOwnerClientRpc + if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) + { + // Clients forward the message to the DAHost who forwards it to the owner + var forwardClientRpc = new ForwardClientRpcMessage() + { + TargetClientIds = clientRpcParams.Send.TargetClientIds.ToArray(), + NetworkDelivery = networkDelivery, + ClientRpcMessage = clientRpcMessage, + }; + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardClientRpc, networkDelivery, NetworkManager.ServerClientId); + } + else // If CMB service connection, then just send to the server (server address) + if (networkManager.CMBServiceConnection) + { + + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); + } + } +#else rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); +#endif } else if (clientRpcParams.Send.TargetClientIdsNativeArray != null) { - foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) { - if (targetClientId == NetworkManager.ServerClientId) + foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) { - shouldSendToHost = true; - break; + if ((networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIdsNativeArray.Contains(OwnerClientId)) + || (!networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIdsNativeArray.Contains(NetworkManager.ServerClientId))) + { + shouldInvokeLocally = true; + continue; + } + // Check to make sure we are sending to only observers, if not log an error. + if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) + { + NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); + } } - - // Check to make sure we are sending to only observers, if not log an error. - if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) + } + else +#endif + { + foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) { - NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); + if (targetClientId == NetworkManager.ServerClientId) + { + shouldInvokeLocally = true; + continue; + } + // Check to make sure we are sending to only observers, if not log an error. + if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) + { + NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); + } } } +#if NGO_DAMODE + // For now, if running as the DAHost or in client-server mode, then send the message to the targeted client identifiers + if (networkManager.DAHost || !networkManager.DistributedAuthorityMode && networkManager.IsServer) + { + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value); + } + else + { + // Clients sending to other clients when in mock DAHost mode need to have the DAHost forward the message via ForwardOwnerClientRpc + if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) + { + // Clients forward the message to the DAHost who forwards it to the owner + var forwardClientRpc = new ForwardClientRpcMessage() + { + TargetClientIds = clientRpcParams.Send.TargetClientIdsNativeArray.Value.ToArray(), + NetworkDelivery = networkDelivery, + ClientRpcMessage = clientRpcMessage, + }; + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardClientRpc, networkDelivery, NetworkManager.ServerClientId); + } + else // If CMB service connection, then just send to the server (server address) + if (networkManager.CMBServiceConnection) + { + // DANGO-TODO: Determine a way to communicate specific client identifiers to the CMB service. + rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); + } + } +#else rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value); +#endif } else { +#if NGO_DAMODE + // For now, if running as the DAHost or in client-server mode, then send the message to the targeted client identifiers + if (networkManager.DAHost || !networkManager.DistributedAuthorityMode && networkManager.IsServer) + { + var observerEnumerator = NetworkObject.Observers.GetEnumerator(); + while (observerEnumerator.MoveNext()) + { + // Skip over the host + if (IsHost && observerEnumerator.Current == NetworkManager.LocalClientId) + { + shouldInvokeLocally = true; + continue; + } + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current); + } + } + else + { + // Clients sending to other clients when in mock DAHost mode need to have the DAHost forward the message via ForwardOwnerClientRpc + if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) + { + if (NetworkObject.Observers.Count > 1) + { + var remoteClients = NetworkObject.Observers.Where((c) => c != NetworkManager.ServerClientId); + + // Clients forward the message to the DAHost who forwards it to the owner + var forwardClientRpc = new ForwardClientRpcMessage() + { + TargetClientIds = remoteClients.ToArray(), + NetworkDelivery = networkDelivery, + ClientRpcMessage = clientRpcMessage, + }; + // Send the forward message + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardClientRpc, networkDelivery, NetworkManager.ServerClientId); + } + // Send the message to the DAHost directly + rpcWriteSize += networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); + } + else // If CMB service connection, then just send to the server (server address) + if (networkManager.CMBServiceConnection) + { + // DANGO-TODO: Determine a way to communicate specific client identifiers to the CMB service. + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); + } + } +#else var observerEnumerator = NetworkObject.Observers.GetEnumerator(); while (observerEnumerator.MoveNext()) { // Skip over the host if (IsHost && observerEnumerator.Current == NetworkManager.LocalClientId) { - shouldSendToHost = true; + shouldInvokeLocally = true; continue; } rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current); } +#endif } // If we are a server/host then we just no op and send to ourself - if (shouldSendToHost) + if (shouldInvokeLocally) { using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp); var context = new NetworkContext { SenderId = NetworkManager.ServerClientId, - Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup, - SystemOwner = NetworkManager, + Timestamp = networkManager.RealTimeProvider.RealTimeSinceStartup, + SystemOwner = networkManager, // header information isn't valid since it's not a real message. // RpcMessage doesn't access this stuff so it's just left empty. Header = new NetworkMessageHeader(), @@ -261,7 +454,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth { foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) { - NetworkManager.NetworkMetrics.TrackRpcSent( + networkManager.NetworkMetrics.TrackRpcSent( targetClientId, NetworkObject, rpcMethodName, @@ -273,7 +466,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth { foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) { - NetworkManager.NetworkMetrics.TrackRpcSent( + networkManager.NetworkMetrics.TrackRpcSent( targetClientId, NetworkObject, rpcMethodName, @@ -286,7 +479,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth var observerEnumerator = NetworkObject.Observers.GetEnumerator(); while (observerEnumerator.MoveNext()) { - NetworkManager.NetworkMetrics.TrackRpcSent( + networkManager.NetworkMetrics.TrackRpcSent( observerEnumerator.Current, NetworkObject, rpcMethodName, @@ -372,6 +565,14 @@ internal void __endSendRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, case SendTo.ClientsAndHost: rpcParams.Send.Target = RpcTarget.ClientsAndHost; break; +#if NGO_DAMODE + case SendTo.Authority: + rpcParams.Send.Target = RpcTarget.Authority; + break; + case SendTo.NotAuthority: + rpcParams.Send.Target = RpcTarget.NotAuthority; + break; +#endif case SendTo.SpecifiedInParams: throw new RpcException("This method requires a runtime-specified send target."); } @@ -456,6 +657,30 @@ public NetworkManager NetworkManager /// public bool IsServer { get; private set; } +#if NGO_DAMODE + + public bool HasAuthority { get; internal set; } + + + internal NetworkClient LocalClient { get; private set; } + + /// + /// Gets if the client is the distributed authority mode session owner + /// + public bool IsSessionOwner + { + get + { + if (LocalClient == null) + { + return false; + } + + return LocalClient.IsSessionOwner; + } + } +#endif + /// /// Gets if the server (local or remote) is a host - i.e., also a client /// @@ -581,30 +806,36 @@ protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId) /// internal void UpdateNetworkProperties() { + var networkObject = NetworkObject; // Set NetworkObject dependent properties - if (NetworkObject != null) + if (networkObject != null) { + var networkManager = NetworkManager; // Set identification related properties - NetworkObjectId = NetworkObject.NetworkObjectId; - IsLocalPlayer = NetworkObject.IsLocalPlayer; + NetworkObjectId = networkObject.NetworkObjectId; + IsLocalPlayer = networkObject.IsLocalPlayer; // This is "OK" because GetNetworkBehaviourOrderIndex uses the order of // NetworkObject.ChildNetworkBehaviours which is set once when first // accessed. - NetworkBehaviourId = NetworkObject.GetNetworkBehaviourOrderIndex(this); + NetworkBehaviourId = networkObject.GetNetworkBehaviourOrderIndex(this); // Set ownership related properties - IsOwnedByServer = NetworkObject.IsOwnedByServer; - IsOwner = NetworkObject.IsOwner; - OwnerClientId = NetworkObject.OwnerClientId; + IsOwnedByServer = networkObject.IsOwnedByServer; + IsOwner = networkObject.IsOwner; + OwnerClientId = networkObject.OwnerClientId; // Set NetworkManager dependent properties - if (NetworkManager != null) + if (networkManager != null) { - IsHost = NetworkManager.IsListening && NetworkManager.IsHost; - IsClient = NetworkManager.IsListening && NetworkManager.IsClient; - IsServer = NetworkManager.IsListening && NetworkManager.IsServer; - ServerIsHost = NetworkManager.IsListening && NetworkManager.ServerIsHost; + IsHost = networkManager.IsListening && networkManager.IsHost; + IsClient = networkManager.IsListening && networkManager.IsClient; + IsServer = networkManager.IsListening && networkManager.IsServer; +#if NGO_DAMODE + LocalClient = networkManager.LocalClient; + HasAuthority = networkObject.HasAuthority; +#endif + ServerIsHost = networkManager.IsListening && networkManager.ServerIsHost; } } else // Shouldn't happen, but if so then set the properties to their default value; @@ -612,9 +843,25 @@ internal void UpdateNetworkProperties() OwnerClientId = NetworkObjectId = default; IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = ServerIsHost = default; NetworkBehaviourId = default; +#if NGO_DAMODE + LocalClient = default; + HasAuthority = default; +#endif } } +#if NGO_DAMODE + /// + /// Distributed Authority Mode Only + /// Invoked only on the authority instance when a is deferring its despawn on non-authoritative instances. + /// + /// + /// See also: + /// + /// the future network tick that the will be despawned on non-authoritative instances + public virtual void OnDeferringDespawn(int despawnTick) { } +#endif + /// /// Gets called when the gets spawned, message handlers are ready to be registered and the network is setup. /// @@ -644,7 +891,12 @@ internal void VisibleOnNetworkSpawn() } InitializeVariables(); + +#if NGO_DAMODE + if (NetworkObject.HasAuthority) +#else if (IsServer) +#endif { // Since we just spawned the object and since user code might have modified their NetworkVariable, esp. // NetworkList, we need to mark the object as free of updates. @@ -681,7 +933,7 @@ internal void InternalOnGainedOwnership() /// /// Invoked on all clients, override this method to be notified of any /// ownership changes (even if the instance was niether the previous or - /// newly assigned current owner). + /// newly assigned current owner). /// /// the previous owner /// the current owner @@ -840,65 +1092,84 @@ internal void PreVariableUpdate() PreNetworkVariableWrite(); } - internal void VariableUpdate(ulong targetClientId) - { - NetworkVariableUpdate(targetClientId, NetworkBehaviourId); - } - internal readonly List NetworkVariableIndexesToReset = new List(); internal readonly HashSet NetworkVariableIndexesToResetSet = new HashSet(); - private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex) + internal void NetworkVariableUpdate(ulong targetClientId) { if (!CouldHaveDirtyNetworkVariables()) { return; } + // Getting these ahead of time actually improves performance + var networkManager = NetworkManager; + var networkObject = NetworkObject; + var behaviourIndex = networkObject.GetNetworkBehaviourOrderIndex(this); + var messageManager = networkManager.MessageManager; + var connectionManager = networkManager.ConnectionManager; + for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++) { + var networkVariable = (NetworkVariableBase)null; var shouldSend = false; for (int k = 0; k < NetworkVariableFields.Count; k++) { - var networkVariable = NetworkVariableFields[k]; + networkVariable = NetworkVariableFields[k]; if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId)) { shouldSend = true; break; } } - - if (shouldSend) +#if NGO_DAMODE + // All of this is just to prevent the DA Host from re-sending a NetworkVariable update it received from the client owner + // If this NetworkManager is running as a DAHost: + // - Only when the write permissions is owner (to pass existing integration tests running as DAHost) + // - If the target client ID is the owner and the owner is not the local NetworkManager instance + // - **Special** As long as ownership did not just change and we are sending the new owner any dirty/updated NetworkVariables + // Under these conditions we should not send to the client + if (shouldSend && networkManager.DAHost && networkVariable.WritePerm == NetworkVariableWritePermission.Owner && + networkObject.OwnerClientId == targetClientId && networkObject.OwnerClientId != networkManager.LocalClientId && + networkObject.PreviousOwnerId == networkObject.OwnerClientId) { - var message = new NetworkVariableDeltaMessage - { - NetworkObjectId = NetworkObjectId, - NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this), - NetworkBehaviour = this, - TargetClientId = targetClientId, - DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j] - }; - // TODO: Serialization is where the IsDirty flag gets changed. - // Messages don't get sent from the server to itself, so if we're host and sending to ourselves, - // we still have to actually serialize the message even though we're not sending it, otherwise - // the dirty flag doesn't change properly. These two pieces should be decoupled at some point - // so we don't have to do this serialization work if we're not going to use the result. - if (IsServer && targetClientId == NetworkManager.ServerClientId) - { - var tmpWriter = new FastBufferWriter(NetworkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, NetworkManager.MessageManager.FragmentedMessageMaxSize); - using (tmpWriter) - { - message.Serialize(tmpWriter, message.Version); - } - } - else + shouldSend = false; + } +#endif + if (!shouldSend) + { + continue; + } + var message = new NetworkVariableDeltaMessage + { + NetworkObjectId = NetworkObjectId, + NetworkBehaviourIndex = behaviourIndex, + NetworkBehaviour = this, + TargetClientId = targetClientId, + DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j] + }; + // TODO: Serialization is where the IsDirty flag gets changed. + // Messages don't get sent from the server to itself, so if we're host and sending to ourselves, + // we still have to actually serialize the message even though we're not sending it, otherwise + // the dirty flag doesn't change properly. These two pieces should be decoupled at some point + // so we don't have to do this serialization work if we're not going to use the result. + if (IsServer && targetClientId == NetworkManager.ServerClientId) + { + var tmpWriter = new FastBufferWriter(messageManager.NonFragmentedMessageMaxSize, Allocator.Temp, messageManager.FragmentedMessageMaxSize); + using (tmpWriter) { - NetworkManager.ConnectionManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId); + message.Serialize(tmpWriter, message.Version); } } + else + { + connectionManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId); + } } } + internal static bool LogSentVariableUpdateMessage; + private bool CouldHaveDirtyNetworkVariables() { // TODO: There should be a better way by reading one dirty variable vs. 'n' @@ -931,14 +1202,65 @@ internal void MarkVariablesDirty(bool dirty) /// internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) { + var networkManager = NetworkManager; +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + writer.WriteValueSafe((ushort)NetworkVariableFields.Count); + } +#endif + if (NetworkVariableFields.Count == 0) { return; } +#if NGO_DAMODE + // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. + // Worth either merging or more cleanly separating these codepaths. for (int j = 0; j < NetworkVariableFields.Count; j++) { + // Note: In distributed authority mode, all clients can read + if (NetworkVariableFields[j].CanClientRead(targetClientId)) + { + if (networkManager.DistributedAuthorityMode) + { + writer.WriteValueSafe(NetworkVariableFields[j].Type); + } + if (networkManager.DistributedAuthorityMode || networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + var writePos = writer.Position; + // Note: This value can't be packed because we don't know how large it will be in advance + // we reserve space for it, then write the data, then come back and fill in the space + // to pack here, we'd have to write data to a temporary buffer and copy it in - which + // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... + // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. + writer.WriteValueSafe((ushort)0); + var startPos = writer.Position; + NetworkVariableFields[j].WriteField(writer); + var size = writer.Position - startPos; + writer.Seek(writePos); + writer.WriteValueSafe((ushort)size); + writer.Seek(startPos + size); + } + else + { + NetworkVariableFields[j].WriteField(writer); + } + } + else + { + // Only if EnsureNetworkVariableLengthSafety, otherwise just skip + if (networkManager.DistributedAuthorityMode || networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + { + writer.WriteValueSafe((ushort)0); + } + } + } +#else + for (int j = 0; j < NetworkVariableFields.Count; j++) + { if (NetworkVariableFields[j].CanClientRead(targetClientId)) { if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) @@ -968,6 +1290,7 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie writer.WriteValueSafe((ushort)0); } } +#endif } /// @@ -980,16 +1303,31 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie /// internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { + var networkManager = NetworkManager; +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + reader.ReadValueSafe(out ushort variableCount); + if (variableCount != NetworkVariableFields.Count) + { + Debug.LogError("NetworkVariable count mismatch."); + return; + } + } +#endif + if (NetworkVariableFields.Count == 0) { return; } + // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. + // Worth either merging or more cleanly separating these codepaths. for (int j = 0; j < NetworkVariableFields.Count; j++) { var varSize = (ushort)0; var readStartPos = 0; - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { reader.ReadValueSafe(out varSize); if (varSize == 0) @@ -1001,12 +1339,39 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) else // If the client cannot read this field, then skip it if (!NetworkVariableFields[j].CanClientRead(clientId)) { +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + reader.ReadValueSafe(out ushort size); + if (size != 0) + { + Debug.LogError("Expected zero size"); + } + } +#endif continue; } - NetworkVariableFields[j].ReadField(reader); +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + // Explicit setting of the NetworkVariableType is only needed for CMB Runtime + reader.ReadValueSafe(out NetworkVariableType _); + reader.ReadValueSafe(out ushort size); + var start_marker = reader.Position; + NetworkVariableFields[j].ReadField(reader); + if (reader.Position - start_marker != size) + { + Debug.LogError("Mismatched network variable size"); + } + } + else +#endif + { + NetworkVariableFields[j].ReadField(reader); + } - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (reader.Position > (readStartPos + varSize)) { @@ -1172,7 +1537,7 @@ internal bool Synchronize(ref BufferSerializer serializer, ulong targetCli { if (NetworkManager.LogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!"); + NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)}({GetType().Name})is being skipped and will not be synchronized!"); } synchronizationError = true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index e6bcfc53c9..6da06eef41 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -44,13 +44,16 @@ internal void NetworkBehaviourUpdate() for (int i = 0; i < m_ConnectionManager.ConnectedClientsList.Count; i++) { var client = m_ConnectionManager.ConnectedClientsList[i]; - +#if NGO_DAMODE + if (m_NetworkManager.DistributedAuthorityMode || dirtyObj.IsNetworkVisibleTo(client.ClientId)) +#else if (dirtyObj.IsNetworkVisibleTo(client.ClientId)) +#endif { // Sync just the variables for just the objects this client sees for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++) { - dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId); + dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId); } } } @@ -69,7 +72,7 @@ internal void NetworkBehaviourUpdate() } for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) { - sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId); + sobj.ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId); } } } @@ -95,6 +98,10 @@ internal void NetworkBehaviourUpdate() foreach (var dirtyobj in m_DirtyNetworkObjects) { dirtyobj.PostNetworkVariableWrite(); +#if NGO_DAMODE + // Once done processing, we set the previous owner id to the current owner id + dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId; +#endif } m_DirtyNetworkObjects.Clear(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b761d69d68..b904c4b16d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using Unity.Collections; +#if NGO_DAMODE +using System.Linq; +#endif using UnityEngine; #if UNITY_EDITOR using UnityEditor; @@ -34,6 +37,140 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem #pragma warning restore IDE1006 // restore naming rule violation check +#if NGO_DAMODE + /// + /// Distributed Authority Mode + /// Returns true if the current session is running in distributed authority mode. + /// + public bool DistributedAuthorityMode + { + get + { + return NetworkConfig.SessionMode == SessionModeTypes.DistributedAuthority; + } + } + + /// + /// Distributed Authority Mode + /// Gets whether the NetworkManager is connected to a distributed authority state service. + /// to determine if the instance is mocking the state service. + /// + public bool CMBServiceConnection + { + get + { + return NetworkConfig.UseCMBService; + } + } + + /// + /// Distributed Authority Mode + /// When enabled, the player prefab will be automatically spawned on the newly connected client-side. + /// + /// + /// Refer to to enable/disable automatic spawning of the player prefab. + /// Alternately, override the to control what prefab the player should spawn. + /// + public bool AutoSpawnPlayerPrefabClientSide + { + get + { + return NetworkConfig.AutoSpawnPlayerPrefabClientSide; + } + } + + /// + /// Distributed Authority Mode + /// Delegate definition for + /// + /// Player Prefab + public delegate GameObject OnFetchLocalPlayerPrefabToSpawnDelegateHandler(); + + /// + /// Distributed Authority Mode + /// When a callback is assigned, this provides control over what player prefab a client will be using. + /// This is invoked only when is enabled. + /// + public OnFetchLocalPlayerPrefabToSpawnDelegateHandler OnFetchLocalPlayerPrefabToSpawn; + + internal GameObject FetchLocalPlayerPrefabToSpawn() + { + if (!AutoSpawnPlayerPrefabClientSide) + { + Debug.LogError($"[{nameof(FetchLocalPlayerPrefabToSpawn)}] Invoked when {nameof(NetworkConfig.AutoSpawnPlayerPrefabClientSide)} was not set! Check call paths!"); + return null; + } + if (OnFetchLocalPlayerPrefabToSpawn == null && NetworkConfig.PlayerPrefab == null) + { + return null; + } + + if (OnFetchLocalPlayerPrefabToSpawn != null) + { + return OnFetchLocalPlayerPrefabToSpawn(); + } + return NetworkConfig.PlayerPrefab; + } + + /// + /// Distributed Authority Mode + /// Gets whether the current NetworkManager is running as a mock distributed authority state service (DAHost) + /// + public bool DAHost + { + get + { + return LocalClient.DAHost; + } + } + + // DANGO-TODO-MVP: Remove these properties once the service handles object distribution + internal ulong ClientToRedistribute; + internal bool RedistributeToClient; + internal int TickToRedistribute; + + internal List DeferredDespawnObjects = new List(); + + public ulong CurrentSessionOwner { get; internal set; } + + internal void SetSessionOwner(ulong sessionOwner) + { + CurrentSessionOwner = sessionOwner; + LocalClient.IsSessionOwner = LocalClientId == sessionOwner; + } + + + +#if NGO_DAMODE + + // TODO: Make this internal after testing + public void PromoteSessionOwner(ulong clientId) + { + if (!DistributedAuthorityMode) + { + NetworkLog.LogErrorServer($"[SceneManagement][NotDA] Invoking promote session owner while not in distributed authority mode!"); + return; + } + if (!DAHost) + { + NetworkLog.LogErrorServer($"[SceneManagement][NotDAHost] Client is attempting to promote another client as the session owner!"); + return; + } + SetSessionOwner(clientId); + var sessionOwnerMessage = new SessionOwnerMessage() + { + SessionOwner = clientId, + }; + var clients = ConnectionManager.ConnectedClientIds.Where(c => c != LocalClientId).ToArray(); + foreach (var targetClient in clients) + { + ConnectionManager.SendMessage(ref sessionOwnerMessage, NetworkDelivery.ReliableSequenced, targetClient); + } + } +#endif + +#endif + public void NetworkUpdate(NetworkUpdateStage updateStage) { switch (updateStage) @@ -56,6 +193,14 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) break; case NetworkUpdateStage.PostLateUpdate: { +#if NGO_DAMODE + // Handle deferred despawning + if (DistributedAuthorityMode) + { + SpawnManager.DeferredDespawnUpdate(ServerTime); + } +#endif + // This should be invoked just prior to the MessageManager processes its outbound queue. SceneManager.CheckForAndSendNetworkObjectSceneChanged(); @@ -71,6 +216,18 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period. DeferredMessageManager.CleanupStaleTriggers(); +#if NGO_DAMODE + // DANGO-TODO-MVP: Remove this once the service handles object distribution + // NOTE: This needs to be the last thing done and should happen exactly at this point + // in the update + if (RedistributeToClient && ServerTime.Tick <= TickToRedistribute) + { + RedistributeToClient = false; + SpawnManager.DistributeNetworkObjects(ClientToRedistribute); + ClientToRedistribute = 0; + } +#endif + if (m_ShuttingDown) { // Host-server will disconnect any connected clients prior to finalizing its shutdown @@ -170,18 +327,32 @@ public ulong LocalClientId internal set => ConnectionManager.LocalClient.ClientId = value; } + /// + /// Gets a dictionary of connected clients and their clientId keys. + /// +#if NGO_DAMODE + public IReadOnlyDictionary ConnectedClients => ConnectionManager.ConnectedClients; +#else /// /// Gets a dictionary of connected clients and their clientId keys. This is only accessible on the server. /// public IReadOnlyDictionary ConnectedClients => IsServer ? ConnectionManager.ConnectedClients : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClients)} should only be accessed on server."); +#endif + /// + /// Gets a list of connected clients. + /// +#if NGO_DAMODE + public IReadOnlyList ConnectedClientsList => ConnectionManager.ConnectedClientsList; +#else /// /// Gets a list of connected clients. This is only accessible on the server. /// public IReadOnlyList ConnectedClientsList => IsServer ? ConnectionManager.ConnectedClientsList : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientsList)} should only be accessed on server."); +#endif /// - /// Gets a list of just the IDs of all connected clients. This is only accessible on the server. + /// Gets a list of just the IDs of all connected clients. /// public IReadOnlyList ConnectedClientsIds => ConnectionManager.ConnectedClientIds; @@ -780,6 +951,10 @@ public int MaximumFragmentedMessageSize internal void Initialize(bool server) { +#if NGO_DAMODE + NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode; +#endif + // Make sure the ServerShutdownState is reset when initializing if (server) { @@ -930,7 +1105,10 @@ public bool StartServer() return false; } - ConnectionManager.LocalClient.SetRole(true, false, this); + if (!ConnectionManager.LocalClient.SetRole(true, false, this)) + { + return false; + } ConnectionManager.LocalClient.ClientId = ServerClientId; Initialize(true); @@ -976,7 +1154,10 @@ public bool StartClient() return false; } - ConnectionManager.LocalClient.SetRole(false, true, this); + if (!ConnectionManager.LocalClient.SetRole(false, true, this)) + { + return false; + } Initialize(false); @@ -1019,7 +1200,11 @@ public bool StartHost() return false; } - ConnectionManager.LocalClient.SetRole(true, true, this); + if (!ConnectionManager.LocalClient.SetRole(true, true, this)) + { + return false; + } + Initialize(true); try { @@ -1072,12 +1257,22 @@ private void HostServerInitialize() } else { +#if NGO_DAMODE + var response = new ConnectionApprovalResponse + { + Approved = true, + // Distributed authority always returns true since the client side handles spawning (whether automatically or manually) + CreatePlayerObject = DistributedAuthorityMode || NetworkConfig.PlayerPrefab != null, + }; + ConnectionManager.HandleConnectionApproval(ServerClientId, response); +#else var response = new ConnectionApprovalResponse { Approved = true, CreatePlayerObject = NetworkConfig.PlayerPrefab != null }; ConnectionManager.HandleConnectionApproval(ServerClientId, response); +#endif } SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); @@ -1224,6 +1419,7 @@ internal void ShutdownInternal() NetworkTickSystem = null; } + // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application. private void OnApplicationQuit() { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 9dea536500..3d83e90961 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; #if UNITY_EDITOR using UnityEditor; @@ -255,7 +256,632 @@ private GlobalObjectId GetGlobalId() /// /// Gets the NetworkManager that owns this NetworkObject instance /// - public NetworkManager NetworkManager => NetworkManagerOwner ?? NetworkManager.Singleton; + public NetworkManager NetworkManager => NetworkManagerOwner ? NetworkManagerOwner : NetworkManager.Singleton; + +#if NGO_DAMODE + /// + /// Useful to know if we should or should not send a message + /// + internal bool HasRemoteObservers => !(Observers.Count() == 0 || (Observers.Contains(NetworkManager.LocalClientId) && Observers.Count() == 1)); + + /// + /// Distributed Authority Mode Only + /// When set, NetworkObjects despawned remotely will be delayed until the tick count specified is reached on all non-owner instances. + /// It will still despawn immediately on the owner-local side. + /// + [HideInInspector] + public int DeferredDespawnTick; + + /// + /// Distributed Authority Mode Only + /// The delegate handler declaration for . + /// + /// true (despawn) or false (do not despawn) + public delegate bool OnDeferedDespawnCompleteDelegateHandler(); + + /// + /// If assigned, this callback will be invoked each frame update to determine if a that has had its despawn deferred + /// should despawn. Use this callback to handle scenarios where you might have additional changes in state that could vindicate despawning earlier + /// than the deferred despawn targeted future network tick. + /// + public OnDeferedDespawnCompleteDelegateHandler OnDeferredDespawnComplete; + + /// + /// Distributed Authority Mode Only + /// When invoked by the authority of the , this will locally despawn the while + /// sending a delayed despawn to all non-authority instances. The tick offset + the authority's current known network tick (ServerTime.Tick) + /// is when non-authority instances will despawn this instance. + /// + /// The number of ticks from the authority's currently known to delay the despawn. + /// Defaults to true, determines whether the will be destroyed. + public void DeferDespawn(int tickOffset, bool destroy = true) + { + if (!NetworkManager.DistributedAuthorityMode) + { + NetworkLog.LogError($"This method is only available in distributed authority mode."); + return; + } + + if (!IsSpawned) + { + NetworkLog.LogError($"Cannot defer despawning {name} because it is not spawned!"); + return; + } + + if (!HasAuthority) + { + NetworkLog.LogError($"Only the authoirty can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!"); + return; + } + + // Apply the relative tick offset for when this NetworkObject should be despawned on + // non-authoritative instances. + DeferredDespawnTick = NetworkManager.ServerTime.Tick + tickOffset; + + var connectionManager = NetworkManager.ConnectionManager; + + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + ChildNetworkBehaviours[i].PreVariableUpdate(); + // Notify all NetworkBehaviours that the authority is performing a deferred despawn. + // This is when user script would update NetworkVariable states that might be needed + // for the deferred despawn sequence on non-authoritative instances. + ChildNetworkBehaviours[i].OnDeferringDespawn(DeferredDespawnTick); + } + + // DAHost handles sending updates to all clients + if (NetworkManager.DAHost) + { + for (int i = 0; i < connectionManager.ConnectedClientsList.Count; i++) + { + var client = connectionManager.ConnectedClientsList[i]; + if (IsNetworkVisibleTo(client.ClientId)) + { + // Sync just the variables for just the objects this client sees + for (int k = 0; k < ChildNetworkBehaviours.Count; k++) + { + ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId); + } + } + } + } + else // Clients just send their deltas to the service or DAHost + { + for (int k = 0; k < ChildNetworkBehaviours.Count; k++) + { + ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId); + } + } + + // Now despawn the local authority instance + Despawn(destroy); + } + + /// + /// When enabled, NetworkObject ownership is distributed amongst clients. + /// To set during runtime, use + /// + /// + /// Scenarios of interest: + /// - If the is locked and the current owner is still connected, then it will not be redistributed upon a new client joining. + /// - If the has an ownership request in progress, then it will not be redistributed upon a new client joining. + /// - If the is locked but the owner is not longer connected, then it will be redistributed. + /// - If the has an ownership request in progress but the target client is no longer connected, then it will be redistributed. + /// + public bool IsOwnershipDistributable => Ownership.HasFlag(OwnershipStatus.Distributable); + + /// + /// Returns true if the is has ownership locked. + /// When locked, the cannot be redistributed nor can it be transferred by another client. + /// To toggle the ownership loked status during runtime, use . + /// + public bool IsOwnershipLocked => ((OwnershipStatusExtended)Ownership).HasFlag(OwnershipStatusExtended.Locked); + + /// + /// When true, the 's ownership can be acquired by any non-owner client. + /// To set during runtime, use . + /// + public bool IsOwnershipTransferable => Ownership.HasFlag(OwnershipStatus.Transferable); + + /// + /// When true, the 's ownership can be acquired through non-owner client requesting ownership. + /// To set during runtime, use + /// To request ownership, use . + /// + public bool IsOwnershipRequestRequired => Ownership.HasFlag(OwnershipStatus.RequestRequired); + + /// + /// When true, the 's ownership cannot be acquired because an ownership request is underway. + /// In order for this status to be applied, the the must have the + /// flag set and a non-owner client must have sent a request via . + /// + public bool IsRequestInProgress => ((OwnershipStatusExtended)Ownership).HasFlag(OwnershipStatusExtended.Requested); + + /// + /// Determines whether a NetworkObject can be distributed to other clients during + /// a session. + /// + [SerializeField] + internal OwnershipStatus Ownership = OwnershipStatus.Distributable; + + /// + /// Ownership status flags: + /// : If nothing is set, then ownership is considered "static" and cannot be redistributed, requested, or transferred (i.e. a Player would have this). + /// : When set, this instance will be automatically redistributed when a client joins (if not locked or no request is pending) or leaves. + /// : When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked). + /// : When set, When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). + /// + // Ranges from 1 to 8 bits + [Flags] + public enum OwnershipStatus + { + None = 0, + Distributable = 1 << 0, + Transferable = 1 << 1, + RequestRequired = 1 << 2, + } + + /// + /// Intentionally internal + /// + // Ranges from 9 to 16 bits + [Flags] + internal enum OwnershipStatusExtended + { + // When locked and CanRequest is set, a non-owner can request ownership. If the owner responds by removing the Locked status, then ownership is transferred. + // If the owner responds by removing the Requested status only, then ownership is denied. + Requested = (1 << 8), + Locked = (1 << 9), + } + + internal bool HasExtendedOwnershipStatus(OwnershipStatusExtended extended) + { + var extendedOwnership = (OwnershipStatusExtended)Ownership; + return extendedOwnership.HasFlag(extended); + } + + internal void AddOwnershipExtended(OwnershipStatusExtended extended) + { + var extendedOwnership = (OwnershipStatusExtended)Ownership; + extendedOwnership |= extended; + Ownership = (OwnershipStatus)extendedOwnership; + } + + internal void RemoveOwnershipExtended(OwnershipStatusExtended extended) + { + var extendedOwnership = (OwnershipStatusExtended)Ownership; + extendedOwnership &= ~extended; + Ownership = (OwnershipStatus)extendedOwnership; + } + + /// + /// Distributed Authority Only + /// Locks ownership of a NetworkObject by the current owner. + /// + /// defaults to lock (true) or unlock (false) + /// true or false depending upon lock operation's success + public bool SetOwnershipLock(bool lockOwnership = true) + { + // If we are not in distributed autority mode, then exit early + if (!NetworkManager.DistributedAuthorityMode) + { + Debug.LogError($"[Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); + return false; + } + + // If we don't have authority exit early + if (!HasAuthority) + { + NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManager.LocalClientId} is trying to lock ownership but does not have authority!"); + return false; + } + + // If we don't have the Transferable flag set and it is not a player object, then it is the same as having a static lock on ownership + if (!IsOwnershipTransferable && !IsPlayerObject) + { + NetworkLog.LogWarning($"Trying to add or remove ownership lock on [{name}] which does not have the {nameof(OwnershipStatus.Transferable)} flag set!"); + return false; + } + + // If we are locking and are already locked or we are unlocking and are already unlocked exit early and return true + if (!(IsOwnershipLocked ^ lockOwnership)) + { + return true; + } + + if (lockOwnership) + { + AddOwnershipExtended(OwnershipStatusExtended.Locked); + } + else + { + RemoveOwnershipExtended(OwnershipStatusExtended.Locked); + } + + SendOwnershipStatusUpdate(); + + return true; + } + + /// + /// In the event of an immediate (local instance) failure to change ownership, the following ownership + /// permission failure status codes will be returned via . + /// : The is locked and ownership cannot be acquired. + /// : The requires an ownership request via . + /// : The already is processing an ownership request and ownership cannot be acquired at this time. + /// does not have the flag set and ownership cannot be acquired. + /// + public enum OwnershipPermissionsFailureStatus + { + Locked, + RequestRequired, + RequestInProgress, + NotTransferrable + } + + /// + /// + /// + /// + public delegate void OnOwnershipPermissionsFailureDelegateHandler(OwnershipPermissionsFailureStatus changeOwnershipFailure); + + /// + /// If there is any callback assigned or subscriptions to this handler, then upon any ownership permissions failure that occurs during + /// the invocation of will trigger this notification containing an . + /// + public OnOwnershipPermissionsFailureDelegateHandler OnOwnershipPermissionsFailure; + + /// + /// Returned by to signify w + /// : The request for ownership was sent (does not mean it will be granted, but the request was sent). + /// : The current client is already the owner (no need to request ownership). + /// : The flag is not set on this + /// : The current owner has locked ownership which means requests are not available at this time. + /// : There is already a known request in progress. You can scan for ownership changes and try upon + /// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership. + /// + public enum OwnershipRequestStatus + { + RequestSent, + AlreadyOwner, + RequestRequiredNotSet, + Locked, + RequestInProgress, + } + + /// + /// Invoke this from a non-authority client to request ownership. + /// + /// + /// The results of requesting ownership: + /// : The request for ownership was sent (does not mean it will be granted, but the request was sent). + /// : The current client is already the owner (no need to request ownership). + /// : The flag is not set on this + /// : The current owner has locked ownership which means requests are not available at this time. + /// : There is already a known request in progress. You can scan for ownership changes and try upon + /// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership. + /// + /// + public OwnershipRequestStatus RequestOwnership() + { + // Exit early the local client is already the owner + if (OwnerClientId == NetworkManager.LocalClientId) + { + return OwnershipRequestStatus.AlreadyOwner; + } + + // Exit early if it doesn't have the RequestRequired flag + if (!IsOwnershipRequestRequired) + { + return OwnershipRequestStatus.RequestRequiredNotSet; + } + + // Exit early if it is locked + if (IsOwnershipLocked) + { + return OwnershipRequestStatus.Locked; + } + + // Exit early if there is already a request in progress + if (IsRequestInProgress) + { + return OwnershipRequestStatus.RequestInProgress; + } + + // Otherwise, send the request ownership message + var changeOwnership = new ChangeOwnershipMessage + { + NetworkObjectId = NetworkObjectId, + OwnerClientId = OwnerClientId, + ClientIdCount = 1, + RequestClientId = NetworkManager.LocalClientId, + ClientIds = new ulong[1] { OwnerClientId }, + DistributedAuthorityMode = true, + RequestOwnership = true, + OwnershipFlags = (ushort)Ownership, + }; + + var sendTarget = NetworkManager.DAHost ? OwnerClientId : NetworkManager.ServerClientId; + NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, NetworkDelivery.Reliable, sendTarget); + + return OwnershipRequestStatus.RequestSent; + } + + /// + /// The delegate handler declaration used by . + /// + /// + /// + public delegate bool OnOwnershipRequestedDelegateHandler(ulong clientRequesting); + + /// + /// The callback that can be used + /// to control when ownership can be transferred to a non-authority client. + /// + /// + /// Requesting ownership requires the flags to have the flag set. + /// + public OnOwnershipRequestedDelegateHandler OnOwnershipRequested; + + /// + /// Invoked by ChangeOwnershipMessage + /// + /// the client requesting ownership + /// + internal void OwnershipRequest(ulong clientRequestingOwnership) + { + var response = OwnershipRequestResponseStatus.Approved; + + // Do a last check to make sure this NetworkObject can be requested + // CMB-DANGO-TODO: We could help optimize this process and check the below flags on the service side. + // It wouldn't cover the scenario were an update was in-bound to the service from the owner, but it would + // handle the case where something had already changed and the service was already "aware" of the change. + if (IsOwnershipLocked) + { + response = OwnershipRequestResponseStatus.Locked; + } + else if (IsRequestInProgress) + { + response = OwnershipRequestResponseStatus.RequestInProgress; + } + else if (!IsOwnershipRequestRequired && !IsOwnershipTransferable) + { + response = OwnershipRequestResponseStatus.CannotRequest; + } + + // Finally, check to see if OnOwnershipRequested is registered and if user script is allowing + // this transfer of ownership + if (OnOwnershipRequested != null && !OnOwnershipRequested.Invoke(clientRequestingOwnership)) + { + response = OwnershipRequestResponseStatus.Denied; + } + + // If we made it here and the response is still approved, then change ownership + if (response == OwnershipRequestResponseStatus.Approved) + { + // When requested and approved, the owner immediately sets the Requested flag **prior to** + // changing the ownership. This prevents race conditions from happening. + // Until the ownership change has propagated out, requests can still flow through this owner, + // but by that time this owner's instance will have the extended Requested flag and will + // respond to any additional ownership request with OwnershipRequestResponseStatus.RequestInProgress. + AddOwnershipExtended(OwnershipStatusExtended.Requested); + + // This action is always authorized as long as the client still has authority. + // We need to pass in that this is a request approval ownership change. + NetworkManager.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); + } + else + { + // Otherwise, send back the reason why the ownership request was denied for the clientRequestingOwnership + /// Notes: + /// We always apply the as opposed to to the + /// value as ownership could have changed and the denied requests + /// targeting this instance are because there is a request pending. + /// DANGO-TODO: What happens if the client requesting disconnects prior to responding with the update in request pending? + var changeOwnership = new ChangeOwnershipMessage + { + NetworkObjectId = NetworkObjectId, + OwnerClientId = NetworkManager.LocalClientId, // Always use the local clientId (see above notes) + RequestClientId = clientRequestingOwnership, + DistributedAuthorityMode = true, + RequestDenied = true, + OwnershipRequestResponseStatus = (byte)response, + OwnershipFlags = (ushort)Ownership, + }; + + var sendTarget = NetworkManager.DAHost ? clientRequestingOwnership : NetworkManager.ServerClientId; + NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, NetworkDelivery.Reliable, sendTarget); + } + } + + /// + /// What is returned via after an ownership request has been sent via + /// + /// + /// Approved: Granted ownership, and returned after the requesting client has gained ownership on the local instance. + /// Locked: Was locked after request was sent. + /// RequestInProgress: A request started before this request was received. + /// CannotRequest: The RequestRequired status changed while the request was in flight. + /// Denied: General denied message that is only set if returns false by the authority instance. + /// + public enum OwnershipRequestResponseStatus + { + Approved, + Locked, + RequestInProgress, + CannotRequest, + Denied, + } + + /// + /// The delegate handler declaration used by . + /// + /// + public delegate void OnOwnershipRequestResponseDelegateHandler(OwnershipRequestResponseStatus ownershipRequestResponse); + + /// + /// The callback that can be used + /// to control when ownership can be transferred to a non-authority client. + /// + /// + /// Requesting ownership requires the flags to have the flag set. + /// + public OnOwnershipRequestResponseDelegateHandler OnOwnershipRequestResponse; + + /// + /// Invoked when a request is denied + /// + internal void OwnershipRequestResponse(OwnershipRequestResponseStatus ownershipRequestResponse) + { + OnOwnershipRequestResponse?.Invoke(ownershipRequestResponse); + } + + /// + /// When passed as a parameter in , the following additional locking actions will occur: + /// - : (default) No locking action + /// - : Will set the passed in flags and then lock the + /// - : Will set the passed in flags and then unlock the + /// + public enum OwnershipLockActions + { + None, + SetAndLock, + SetAndUnlock + } + + /// + /// Adds an flag to the flags + /// + /// flag(s) to update + /// defaults to false, but when true will clear the permissions and then set the permissions flags + /// defaults to , but when set it to anther action type it will either lock or unlock ownership after setting the flags + /// true (applied)/false (not applied) + /// + /// If it returns false, then this means the flag(s) you are attempting to + /// set were already set on the instance. + /// If it returns true, then the flags were set and an ownership update message + /// was sent to all observers of the instance. + /// + public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, OwnershipLockActions lockAction = OwnershipLockActions.None) + { + // If it already has the flag do nothing + if (!clearAndSet && Ownership.HasFlag(status)) + { + return false; + } + + if (clearAndSet || status == OwnershipStatus.None) + { + Ownership = OwnershipStatus.None; + } + + // Faster to just OR a None status than to check + // if it is !None before "OR'ing". + Ownership |= status; + + if (lockAction != OwnershipLockActions.None) + { + SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock); + } + + SendOwnershipStatusUpdate(); + + return true; + } + + /// + /// Use this method to remove one or more ownership flags from the NetworkObject. + /// If you want to clear and then set, use . + /// + /// the flag(s) to remove + /// true/false + /// + /// If it returns false, then this means the flag(s) you are attempting to + /// remove were not already set on the instance. + /// If it returns true, then the flags were removed and an ownership update message + /// was sent to all observers of the instance. + /// + public bool RemoveOwnershipStatus(OwnershipStatus status) + { + // If it doesn't have the ownership flag or we are trying to remove the None permission, then return false + if (!Ownership.HasFlag(status) || status == OwnershipStatus.None) + { + return false; + } + + Ownership &= ~status; + + SendOwnershipStatusUpdate(); + + return true; + } + + /// + /// Sends an update ownership status to all non-owner clients + /// + internal void SendOwnershipStatusUpdate() + { + // If there are no remote observers, then exit early + if (!HasRemoteObservers) + { + return; + } + + var changeOwnership = new ChangeOwnershipMessage + { + NetworkObjectId = NetworkObjectId, + OwnerClientId = OwnerClientId, + DistributedAuthorityMode = true, + OwnershipFlagsUpdate = true, + OwnershipFlags = (ushort)Ownership, + }; + + if (NetworkManager.DAHost) + { + foreach (var clientId in Observers) + { + if (clientId == NetworkManager.LocalClientId) + { + continue; + } + NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, NetworkDelivery.Reliable, clientId); + } + } + else + { + changeOwnership.ClientIdCount = Observers.Count(); + changeOwnership.ClientIds = Observers.ToArray(); + NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, NetworkDelivery.Reliable, NetworkManager.ServerClientId); + } + } + + /// + /// Use this method to determine if a has one or more ownership flags set. + /// + /// one or more flags + /// true if the flag(s) are set and false if the flag or any one of the flags are not set + public bool HasOwnershipStatus(OwnershipStatus status) + { + return Ownership.HasFlag(status); + } + + /// + /// This property can be used in client-server or distributed authority modes to determine if the local instance has authority. + /// When in client-server mode, the server will always have authority over the NetworkObject and associated NetworkBehaviours. + /// When in distributed authority mode, the owner is always the authority. + /// + /// + /// When in client-server mode, authority should is not considered the same as ownership. + /// + public bool HasAuthority => InternalHasAuthority(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool InternalHasAuthority() + { + var networkManager = NetworkManager; + return networkManager.DistributedAuthorityMode ? OwnerClientId == networkManager.LocalClientId : networkManager.IsServer; + } +#endif /// /// The NetworkManager that owns this NetworkObject. @@ -275,6 +901,10 @@ private GlobalObjectId GetGlobalId() /// public ulong OwnerClientId { get; internal set; } +#if NGO_DAMODE + internal ulong PreviousOwnerId; +#endif + /// /// If true, the object will always be replicated as root on clients and the parent will be ignored. /// @@ -322,6 +952,13 @@ private GlobalObjectId GetGlobalId() /// public bool? IsSceneObject { get; internal set; } +#if NGO_DAMODE + public void SetSceneObjectStatus(bool isSceneObject = false) + { + IsSceneObject = isSceneObject; + } +#endif + /// /// Gets whether or not the object should be automatically removed when the scene is unloaded. /// @@ -449,6 +1086,7 @@ public HashSet.Enumerator GetObservers() /// /// The clientId of the client /// True if the client knows about the object + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNetworkVisibleTo(ulong clientId) { if (!IsSpawned) @@ -537,6 +1175,32 @@ public void NetworkShow(ulong clientId) throw new SpawnStateException("Object is not spawned"); } +#if NGO_DAMODE + if (!HasAuthority) + { + if (NetworkManager.DistributedAuthorityMode) + { + throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!"); + } + else + { + throw new NotServerException("Only the authority can change visibility"); + } + } + + if (Observers.Contains(clientId)) + { + if (NetworkManager.DistributedAuthorityMode) + { + Debug.LogError($"The object {name} is already visible to Client-{clientId}!"); + return; + } + else + { + throw new NotServerException("Only server can change visibility"); + } + } +#else if (!NetworkManager.IsServer) { throw new NotServerException("Only server can change visibility"); @@ -544,8 +1208,10 @@ public void NetworkShow(ulong clientId) if (Observers.Contains(clientId)) { - throw new VisibilityChangeException("The object is already visible"); + throw new VisibilityChangeException($"The object {name} is already visible to Client-{clientId}!"); } +#endif + if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) { @@ -579,19 +1245,51 @@ public static void NetworkShow(List networkObjects, ulong clientI { if (networkObjects == null || networkObjects.Count == 0) { - throw new ArgumentNullException("At least one " + nameof(NetworkObject) + " has to be provided"); + NetworkLog.LogErrorServer($"At least one {nameof(NetworkObject)} has to be provided when showing a list of {nameof(NetworkObject)}s!"); + return; } - NetworkManager networkManager = networkObjects[0].NetworkManager; - +#if !NGO_DAMODE + var networkManager = networkObjects[0].NetworkManager; if (!networkManager.IsServer) { throw new NotServerException("Only server can change visibility"); } +#endif // Do the safety loop first to prevent putting the netcode in an invalid state. for (int i = 0; i < networkObjects.Count; i++) { +#if NGO_DAMODE + var networkObject = networkObjects[i]; + var networkManager = networkObject.NetworkManager; + + if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) + { + NetworkLog.LogErrorServer($"Cannot hide an object from the owner when distributed authority mode is enabled! (Skipping {networkObject.gameObject.name})"); + } + else if (!networkManager.DistributedAuthorityMode && clientId == NetworkManager.ServerClientId) + { + NetworkLog.LogErrorServer("Cannot hide an object from the server!"); + continue; + } + + // Distributed authority mode adjustments to log a network error and continue when trying to show a NetworkObject + // that the local instance does not own + if (!networkObjects[i].HasAuthority) + { + if (networkObjects[i].NetworkManager.DistributedAuthorityMode) + { + // It will log locally and to the "master-host". + NetworkLog.LogErrorServer("Only the owner-authority can change visibility when distributed authority mode is enabled!"); + continue; + } + else + { + throw new NotServerException("Only server can change visibility"); + } + } +#endif if (!networkObjects[i].IsSpawned) { throw new SpawnStateException("Object is not spawned"); @@ -635,6 +1333,19 @@ public void NetworkHide(ulong clientId) throw new SpawnStateException("Object is not spawned"); } +#if NGO_DAMODE + if (!HasAuthority && !NetworkManager.DAHost) + { + if (NetworkManager.DistributedAuthorityMode) + { + throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!"); + } + else + { + throw new NotServerException("Only the authority can change visibility"); + } + } +#else if (!NetworkManager.IsServer) { throw new NotServerException("Only server can change visibility"); @@ -644,22 +1355,66 @@ public void NetworkHide(ulong clientId) { throw new VisibilityChangeException("Cannot hide an object from the server"); } +#endif if (!NetworkManager.SpawnManager.RemoveObjectFromShowingTo(this, clientId)) { if (!Observers.Contains(clientId)) { +#if NGO_DAMODE + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"{name} is already hidden from Client-{clientId}! (ignoring)"); + return; + } +#else throw new VisibilityChangeException("The object is already hidden"); +#endif } Observers.Remove(clientId); var message = new DestroyObjectMessage { NetworkObjectId = NetworkObjectId, - DestroyGameObject = !IsSceneObject.Value + DestroyGameObject = !IsSceneObject.Value, +#if NGO_DAMODE + IsDistributedAuthority = NetworkManager.DistributedAuthorityMode, + IsTargetedDestroy = NetworkManager.DistributedAuthorityMode, + TargetClientId = clientId, // Just always populate this value whether we write it or not + DeferredDespawnTick = DeferredDespawnTick, +#endif }; - // Send destroy call - var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + + var size = 0; +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + if (!NetworkManager.DAHost) + { + // Send destroy call to service or DAHost + size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ServerClientId); + } + else // DAHost mocking service + { + // Send destroy call + size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + // Broadcast the destroy to all clients so they can update their observers list + foreach (var client in NetworkManager.ConnectedClientsIds) + { + if (client == clientId || client == NetworkManager.LocalClientId) + { + continue; + } + size += NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client); + } + } + } + else +#endif + { + // Send destroy call + size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + } NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size); } } @@ -681,6 +1436,14 @@ public void NetworkHide(ulong clientId) /// The targeted client public static void NetworkHide(List networkObjects, ulong clientId) { + +#if NGO_DAMODE + if (networkObjects == null || networkObjects.Count == 0) + { + NetworkLog.LogErrorServer($"At least one {nameof(NetworkObject)} has to be provided when hiding a list of {nameof(NetworkObject)}s!"); + return; + } +#else if (networkObjects == null || networkObjects.Count == 0) { throw new ArgumentNullException("At least one " + nameof(NetworkObject) + " has to be provided"); @@ -697,10 +1460,44 @@ public static void NetworkHide(List networkObjects, ulong clientI { throw new VisibilityChangeException("Cannot hide an object from the server"); } +#endif + + // Do the safety loop first to prevent putting the netcode in an invalid state. + for (int i = 0; i < networkObjects.Count; i++) + { + +#if NGO_DAMODE + var networkObject = networkObjects[i]; + var networkManager = networkObject.NetworkManager; + + if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) + { + NetworkLog.LogErrorServer($"Cannot hide an object from the owner when distributed authority mode is enabled! (Skipping {networkObject.gameObject.name})"); + } + else if (!networkManager.DistributedAuthorityMode && clientId == NetworkManager.ServerClientId) + { + NetworkLog.LogErrorServer("Cannot hide an object from the server!"); + continue; + } + + // Distributed authority mode adjustments to log a network error and continue when trying to show a NetworkObject + // that the local instance does not own + if (!networkObjects[i].HasAuthority) + { + if (networkObjects[i].NetworkManager.DistributedAuthorityMode) + { + // It will log locally and to the "master-host". + NetworkLog.LogErrorServer($"Only the owner-authority can change hide a {nameof(NetworkObject)} when distributed authority mode is enabled!"); + continue; + } + else + { + throw new NotServerException("Only server can change visibility!"); + } + } - // Do the safety loop first to prevent putting the netcode in an invalid state. - for (int i = 0; i < networkObjects.Count; i++) - { + // CLIENT SPAWNING TODO: Log error and continue as opposed to throwing an exception +#endif if (!networkObjects[i].IsSpawned) { throw new SpawnStateException("Object is not spawned"); @@ -731,7 +1528,14 @@ private void OnDestroy() return; } - if (NetworkManager.IsListening && !NetworkManager.IsServer && IsSpawned && +#if NGO_DAMODE + // Authority is the server (client-server) and the owner or DAHost (distributed authority) when destroying a NetworkObject + var isAuthority = HasAuthority || NetworkManager.DAHost; +#else + var isAuthority = NetworkManager.IsServer; +#endif + + if (NetworkManager.IsListening && !isAuthority && IsSpawned && (IsSceneObject == null || (IsSceneObject.Value != true))) { // Clients should not despawn NetworkObjects while connected to a session, but we don't want to destroy the current call stack @@ -739,10 +1543,21 @@ private void OnDestroy() if (!NetworkManager.ShutdownInProgress) { // Since we still have a session connection, log locally and on the server to inform user of this issue. +#if NGO_DAMODE if (NetworkManager.LogLevel <= LogLevel.Error) { - NetworkLog.LogErrorServer($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); + if (NetworkManager.DistributedAuthorityMode) + { + NetworkLog.LogError($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-owner client is not valid during a distributed authority session. Call {nameof(Destroy)} or {nameof(Despawn)} on the client-owner instead."); + } + else + { + NetworkLog.LogErrorServer($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); + } } +#else + NetworkLog.LogErrorServer($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); +#endif return; } // Otherwise, clients can despawn NetworkObjects while shutting down and should not generate any messages when this happens @@ -760,18 +1575,77 @@ private void OnDestroy() [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject) { + if (NetworkManagerOwner == null) + { + NetworkManagerOwner = NetworkManager.Singleton; + } if (!NetworkManager.IsListening) { throw new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before spawning objects"); } +#if NGO_DAMODE + if ((!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) || (NetworkManager.DistributedAuthorityMode && !NetworkManager.LocalClient.IsSessionOwner && NetworkManager.LocalClientId != ownerClientId)) + { + if (NetworkManager.DistributedAuthorityMode) + { + throw new NotServerException($"When distributed authority mode is enabled, you can only spawn NetworkObjects that belong to the local instance! Local instance id {NetworkManager.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!"); + } + else + { + throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s"); + } + } + + if (NetworkManager.DistributedAuthorityMode) + { + if (NetworkManager.NetworkConfig.EnableSceneManagement) + { + NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; + } + if (DontDestroyWithOwner && !IsOwnershipDistributable) + { + //Ownership |= OwnershipStatus.Distributable; + // DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will automatically set DistributeOwnership."); + } + } + } +#else if (!NetworkManager.IsServer) { throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s"); } +#endif NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); +#if NGO_DAMODE + if ((NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) || (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer)) + { + for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) + { + if (NetworkManager.ConnectedClientsList[i].ClientId == NetworkManager.ServerClientId) + { + continue; + } + if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId)) + { + NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ConnectedClientsList[i].ClientId, this); + } + } + } + else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) + { + NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this); + } + else + { + NetworkLog.LogWarningServer($"Ran into unknown conditional check during spawn when determining distributed authority mode or not"); + } +#else for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) { if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId)) @@ -779,6 +1653,8 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ConnectedClientsList[i].ClientId, this); } } +#endif + } /// @@ -831,26 +1707,32 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow return null; } +#if NGO_DAMODE + ownerClientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; + // We only need to check for authority when running in client-server mode + if (!networkManager.IsServer && !networkManager.DistributedAuthorityMode) +#else if (!networkManager.IsServer) +#endif { Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NotAuthority]); return null; } - if (NetworkManager.ShutdownInProgress) + if (networkManager.ShutdownInProgress) { Debug.LogWarning(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown]); return null; } // Verify it is actually a valid prefab - if (!NetworkManager.NetworkConfig.Prefabs.Contains(gameObject)) + if (!networkManager.NetworkConfig.Prefabs.Contains(gameObject)) { Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab]); return null; } - return NetworkManager.SpawnManager.InstantiateAndSpawnNoParameterChecks(this, ownerClientId, destroyWithScene, isPlayerObject, forceOverride, position, rotation); + return networkManager.SpawnManager.InstantiateAndSpawnNoParameterChecks(this, ownerClientId, destroyWithScene, isPlayerObject, forceOverride, position, rotation); } /// @@ -859,7 +1741,12 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow /// Should the object be destroyed when the scene is changed public void Spawn(bool destroyWithScene = false) { +#if NGO_DAMODE + var clientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; + SpawnInternal(destroyWithScene, clientId, false); +#else SpawnInternal(destroyWithScene, NetworkManager.ServerClientId, false); +#endif } /// @@ -906,17 +1793,32 @@ public void RemoveOwnership() /// The new owner clientId public void ChangeOwnership(ulong newOwnerClientId) { +#if NGO_DAMODE + NetworkManager.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); +#else NetworkManager.SpawnManager.ChangeOwnership(this, newOwnerClientId); +#endif } internal void InvokeBehaviourOnLostOwnership() { - // Server already handles this earlier, hosts should ignore, all clients should update +#if NGO_DAMODE + // Always update the ownership table in distributed authority mode + if (NetworkManager.DistributedAuthorityMode) + { + NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); + } + else // Server already handles this earlier, hosts should ignore and only client owners should update if (!NetworkManager.IsServer) { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); } - +#else + if (!NetworkManager.IsServer) + { + NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); + } +#endif for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { ChildNetworkBehaviours[i].InternalOnLostOwnership(); @@ -925,11 +1827,24 @@ internal void InvokeBehaviourOnLostOwnership() internal void InvokeBehaviourOnGainedOwnership() { - // Server already handles this earlier, hosts should ignore and only client owners should update + +#if NGO_DAMODE + // Always update the ownership table in distributed authority mode + if (NetworkManager.DistributedAuthorityMode) + { + NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); + } + else // Server already handles this earlier, hosts should ignore and only client owners should update + if (!NetworkManager.IsServer && NetworkManager.LocalClientId == OwnerClientId) + { + NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); + } +#else if (!NetworkManager.IsServer && NetworkManager.LocalClientId == OwnerClientId) { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); } +#endif for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { @@ -971,6 +1886,15 @@ internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNe private Transform m_CachedParent; // What is our last set parent Transform reference? private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent +#if NGO_DAMODE + /// + /// With distributed authority, we need to have a way to determine if the parenting action is authorized. + /// This is set when handling an incoming ParentSyncMessage and when running as a DAHost and a client has disconnected. + /// + internal bool AuthorityAppliedParenting = false; +#endif + + /// /// Returns the last known cached WorldPositionStays value for this NetworkObject /// @@ -989,6 +1913,9 @@ public bool WorldPositionStays() internal void SetCachedParent(Transform parentTransform) { +#if NGO_DAMODE + AuthorityAppliedParenting = false; +#endif m_CachedParent = parentTransform; } @@ -1050,7 +1977,7 @@ public bool TrySetParent(GameObject parent, bool worldPositionStays = true) /// internal bool TryRemoveParentCachedWorldPositionStays() { - return TrySetParent((NetworkObject)null, m_CachedWorldPositionStays); + return InternalTrySetParent(null, m_CachedWorldPositionStays); } /// @@ -1084,19 +2011,32 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) return false; } - if (!NetworkManager.IsServer && !NetworkManager.ShutdownInProgress) +#if NGO_DAMODE + // DANGO-TODO: Do we want to worry about ownership permissions here? + // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. + var isAuthority = HasAuthority; +#else + var isAuthority = NetworkManager.IsServer; +#endif + // If we don't have authority and we are not shutting down, then don't allow any parenting. + // If we are shutting down and don't have authority then allow it. + if (!isAuthority && !NetworkManager.ShutdownInProgress) { return false; } - // If the parent is not null fail only if either of the two is true: - // - This instance is spawned and the parent is not. - // - This instance is not spawned and the parent is. - // Basically, don't allow parenting when either the child or parent is not spawned. - // Caveat: if the parent is null then we can allow parenting whether the instance is or is not spawned. + return InternalTrySetParent(parent, worldPositionStays); + } + + internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) + { + if (parent != null && (IsSpawned ^ parent.IsSpawned)) { - return false; + if (NetworkManager != null && !NetworkManager.ShutdownInProgress) + { + return false; + } } m_CachedWorldPositionStays = worldPositionStays; @@ -1115,7 +2055,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) private void OnTransformParentChanged() { - if (!AutoObjectParentSync) + if (!AutoObjectParentSync || NetworkManager.ShutdownInProgress) { return; } @@ -1131,32 +2071,48 @@ private void OnTransformParentChanged() Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before reparenting")); return; } - - if (!NetworkManager.IsServer) + var isAuthority = false; +#if NGO_DAMODE + // With distributed authority, we need to track "valid authoritative" parenting changes. + // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". + isAuthority = HasAuthority || AuthorityAppliedParenting; + var distributedAuthority = NetworkManager.DistributedAuthorityMode; +#else + isAuthority = NetworkManager.IsServer; +#endif + // If we do not have authority and we are spawned + if (!isAuthority && IsSpawned) { - // Log exception if we are a client and not shutting down. - if (!NetworkManager.ShutdownInProgress) +#if NGO_DAMODE + // If the cached parent has not already been set and we are in distributed authority mode, then log an exception and exit early as a non-authority instance + // is trying to set the parent. + if (distributedAuthority) { transform.parent = m_CachedParent; - Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); + NetworkLog.LogError($"[Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can reparent it!"); } - else // Otherwise, if we are removing a parent then go ahead and allow parenting to occur - if (transform.parent == null) + else { - m_LatestParent = null; - m_CachedParent = null; - InvokeBehaviourOnNetworkObjectParentChanged(null); + transform.parent = m_CachedParent; + Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); } return; +#else + Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); + return; +#endif } - else // Otherwise, on the serer side if this instance is not spawned... + if (!IsSpawned) { - // ,,,and we are removing the parent, then go ahead and allow parenting to occur +#if NGO_DAMODE + AuthorityAppliedParenting = false; +#endif + // and we are removing the parent, then go ahead and allow parenting to occur if (transform.parent == null) { m_LatestParent = null; - m_CachedParent = null; + SetCachedParent(null); InvokeBehaviourOnNetworkObjectParentChanged(null); } else @@ -1173,13 +2129,18 @@ private void OnTransformParentChanged() if (!transform.parent.TryGetComponent(out var parentObject)) { transform.parent = m_CachedParent; +#if NGO_DAMODE + AuthorityAppliedParenting = false; +#endif Debug.LogException(new InvalidParentException($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent")); return; } - - if (!parentObject.IsSpawned) + else if (!parentObject.IsSpawned) { transform.parent = m_CachedParent; +#if NGO_DAMODE + AuthorityAppliedParenting = false; +#endif Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented under another spawned {nameof(NetworkObject)}")); return; } @@ -1192,6 +2153,10 @@ private void OnTransformParentChanged() removeParent = m_CachedParent != null; } +#if NGO_DAMODE + // This can be reset within ApplyNetworkParenting + var authorityApplied = AuthorityAppliedParenting; +#endif ApplyNetworkParenting(removeParent); var message = new ParentSyncMessage @@ -1200,6 +2165,9 @@ private void OnTransformParentChanged() IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue, LatestParent = m_LatestParent, RemoveParent = removeParent, +#if NGO_DAMODE + AuthorityApplied = authorityApplied, +#endif WorldPositionStays = m_CachedWorldPositionStays, Position = m_CachedWorldPositionStays ? transform.position : transform.localPosition, Rotation = m_CachedWorldPositionStays ? transform.rotation : transform.localRotation, @@ -1214,6 +2182,53 @@ private void OnTransformParentChanged() m_CachedWorldPositionStays = true; } +#if NGO_DAMODE + // If we are connected to a CMB service or we are running a mock CMB service then send to the "server" identifier + if (distributedAuthority) + { + if (!NetworkManager.DAHost) + { + NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, 0); + return; + } + else + { + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (clientId == NetworkManager.ServerClientId) + { + continue; + } + NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + } + } + } + else + { + // Otherwise we are running in client-server =or= this has to be a DAHost instance. + // Send to all connected clients. + unsafe + { + var maxCount = NetworkManager.ConnectedClientsIds.Count; + ulong* clientIds = stackalloc ulong[maxCount]; + int idx = 0; + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (clientId == NetworkManager.ServerClientId) + { + continue; + } + if (Observers.Contains(clientId)) + { + clientIds[idx++] = clientId; + } + } + NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx); + } + } +#else + // Otherwise we are running in client-server =or= this has to be a DAHost instance. + // Send to all connected clients. unsafe { var maxCount = NetworkManager.ConnectedClientsIds.Count; @@ -1226,9 +2241,9 @@ private void OnTransformParentChanged() clientIds[idx++] = clientId; } } - NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx); } +#endif } // We're keeping this set called OrphanChildren which contains NetworkObjects @@ -1294,7 +2309,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa SetNetworkParenting(parentNetworkObject.NetworkObjectId, false); // Set the cached parent - m_CachedParent = parentNetworkObject.transform; + SetCachedParent(parentNetworkObject.transform); return true; } @@ -1308,7 +2323,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // or a parent was removed prior to the client connecting (i.e. in-scene placed NetworkObjects) if (removeParent || !m_LatestParent.HasValue) { - m_CachedParent = null; + SetCachedParent(null); // We must use Transform.SetParent when taking WorldPositionStays into // consideration, otherwise just setting transform.parent = null defaults // to WorldPositionStays which can cause scaling issues if the parent's @@ -1337,8 +2352,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa return false; } } - - m_CachedParent = parentObject.transform; + SetCachedParent(parentObject.transform); transform.SetParent(parentObject.transform, m_CachedWorldPositionStays); InvokeBehaviourOnNetworkObjectParentChanged(parentObject); return true; @@ -1421,6 +2435,16 @@ internal List ChildNetworkBehaviours internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) { +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + writer.WriteValueSafe((ushort)ChildNetworkBehaviours.Count); + if (ChildNetworkBehaviours.Count == 0) + { + return; + } + } +#endif for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { var behavior = ChildNetworkBehaviours[i]; @@ -1463,17 +2487,31 @@ internal static void VerifyParentingStatus() /// /// Only invoked during first synchronization of a NetworkObject (late join or newly spawned) /// - internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) + internal bool SetNetworkVariableData(FastBufferReader reader, ulong clientId) { +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + var readerPosition = reader.Position; + reader.ReadValueSafe(out ushort behaviorCount); + if (behaviorCount != ChildNetworkBehaviours.Count) + { + Debug.LogError($"Network Behavior Count Mismatch! [{readerPosition}][{reader.Position}]"); + return false; + } + } +#endif + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { var behaviour = ChildNetworkBehaviours[i]; behaviour.InitializeVariables(); behaviour.SetNetworkVariableData(reader, clientId); } + return true; } - internal ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance) + public ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance) { // read the cached index, and verify it first if (instance.NetworkBehaviourIdCache < ChildNetworkBehaviours.Count) @@ -1500,7 +2538,7 @@ internal ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance) return 0; } - public NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) + internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) { if (index >= ChildNetworkBehaviours.Count) { @@ -1528,11 +2566,15 @@ public NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) internal struct SceneObject { - private byte m_BitField; + private ushort m_BitField; public uint Hash; public ulong NetworkObjectId; public ulong OwnerClientId; +#if NGO_DAMODE + public ushort OwnershipFlags; +#endif + public bool IsPlayerObject { get => ByteUtility.GetBit(m_BitField, 0); @@ -1578,6 +2620,37 @@ public bool DestroyWithScene set => ByteUtility.SetBit(ref m_BitField, 6, value); } +#if NGO_DAMODE + public bool DontDestroyWithOwner + { + get => ByteUtility.GetBit(m_BitField, 7); + set => ByteUtility.SetBit(ref m_BitField, 7, value); + } + + public bool HasOwnershipFlags + { + get => ByteUtility.GetBit(m_BitField, 8); + set => ByteUtility.SetBit(ref m_BitField, 8, value); + } + + public bool SyncObservers + { + get => ByteUtility.GetBit(m_BitField, 9); + set => ByteUtility.SetBit(ref m_BitField, 9, value); + } + + public bool SpawnWithObservers + { + get => ByteUtility.GetBit(m_BitField, 10); + set => ByteUtility.SetBit(ref m_BitField, 10, value); + } + + // When handling the initial synchronization of NetworkObjects, + // this will be populated with the known observers. + public ulong[] Observers; + +#endif + //If(Metadata.HasParent) public ulong ParentObjectId; @@ -1604,6 +2677,13 @@ public struct TransformData : INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { +#if NGO_DAMODE + if (OwnerObject.NetworkManager.DistributedAuthorityMode) + { + HasOwnershipFlags = true; + SpawnWithObservers = OwnerObject.SpawnWithObservers; + } +#endif writer.WriteValueSafe(m_BitField); writer.WriteValueSafe(Hash); BytePacker.WriteValueBitPacked(writer, NetworkObjectId); @@ -1618,6 +2698,22 @@ public void Serialize(FastBufferWriter writer) } } +#if NGO_DAMODE + if (HasOwnershipFlags) + { + writer.WriteValueSafe(OwnershipFlags); + } + + if (SyncObservers) + { + BytePacker.WriteValuePacked(writer, Observers.Length); + foreach (var observer in Observers) + { + BytePacker.WriteValuePacked(writer, observer); + } + } +#endif + var writeSize = 0; writeSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; writeSize += FastBufferWriter.GetWriteSize(); @@ -1634,7 +2730,18 @@ public void Serialize(FastBufferWriter writer) // The NetworkSceneHandle is the server-side relative // scene handle that the NetworkObject resides in. +#if NGO_DAMODE + if (OwnerObject.NetworkManager.DistributedAuthorityMode) + { + writer.WriteValue(OwnerObject.NetworkSceneHandle); + } + else + { + writer.WriteValue(OwnerObject.GetSceneOriginHandle()); + } +#else writer.WriteValue(OwnerObject.GetSceneOriginHandle()); +#endif // Synchronize NetworkVariables and NetworkBehaviours var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); @@ -1658,6 +2765,26 @@ public void Deserialize(FastBufferReader reader) } } +#if NGO_DAMODE + if (HasOwnershipFlags) + { + reader.ReadValueSafe(out OwnershipFlags); + } + + if (SyncObservers) + { + var observerCount = 0; + var observerId = (ulong)0; + ByteUnpacker.ReadValuePacked(reader, out observerCount); + Observers = new ulong[observerCount]; + for (int i = 0; i < observerCount; i++) + { + ByteUnpacker.ReadValuePacked(reader, out observerId); + Observers[i] = observerId; + } + } +#endif + var readSize = 0; readSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; readSize += FastBufferWriter.GetWriteSize(); @@ -1737,29 +2864,51 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer } else { + var seekToEndOfSynchData = 0; var reader = serializer.GetFastBufferReader(); + try + { + reader.ReadValueSafe(out ushort sizeOfSynchronizationData); + seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData; + // Apply the network variable synchronization data + if (!SetNetworkVariableData(reader, targetClientId)) + { + reader.Seek(seekToEndOfSynchData); + return; + } + + // Read the number of NetworkBehaviours to synchronize + reader.ReadValueSafe(out byte numberSynchronized); - reader.ReadValueSafe(out ushort sizeOfSynchronizationData); - var seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData; - // Apply the network variable synchronization data - SetNetworkVariableData(reader, targetClientId); - // Read the number of NetworkBehaviours to synchronize - reader.ReadValueSafe(out byte numberSynchronized); - var networkBehaviourId = (ushort)0; + var networkBehaviourId = (ushort)0; + + // If a NetworkBehaviour writes synchronization data, it will first + // write its NetworkBehaviourId so when deserializing the client-side + // can find the right NetworkBehaviour to deserialize the synchronization data. + for (int i = 0; i < numberSynchronized; i++) + { + reader.ReadValueSafe(out networkBehaviourId); + var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId); + networkBehaviour.Synchronize(ref serializer, targetClientId); + } - // If a NetworkBehaviour writes synchronization data, it will first - // write its NetworkBehaviourId so when deserializing the client-side - // can find the right NetworkBehaviour to deserialize the synchronization data. - for (int i = 0; i < numberSynchronized; i++) + if (seekToEndOfSynchData != reader.Position) + { + Debug.LogWarning($"[Size mismatch] Expected: {seekToEndOfSynchData} Currently At: {reader.Position}!"); + } + } + catch { - serializer.SerializeValue(ref networkBehaviourId); - var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId); - networkBehaviour.Synchronize(ref serializer, targetClientId); + reader.Seek(seekToEndOfSynchData); } } } +#if NGO_DAMODE + internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) +#else internal SceneObject GetMessageSceneObject(ulong targetClientId) +#endif { var obj = new SceneObject { @@ -1768,6 +2917,14 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) IsPlayerObject = IsPlayerObject, IsSceneObject = IsSceneObject ?? true, DestroyWithScene = DestroyWithScene, +#if NGO_DAMODE + DontDestroyWithOwner = DontDestroyWithOwner, + HasOwnershipFlags = NetworkManager.DistributedAuthorityMode, + OwnershipFlags = (ushort)Ownership, + SyncObservers = syncObservers, + Observers = syncObservers ? Observers.ToArray() : null, + NetworkSceneHandle = NetworkSceneHandle, +#endif Hash = HostCheckForGlobalObjectIdHashOverride(), OwnerObject = this, TargetClientId = targetClientId @@ -1842,7 +2999,6 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) Scale = syncScaleLocalSpaceRelative ? transform.localScale : transform.lossyScale, }; } - return obj; } @@ -1853,8 +3009,14 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) /// Deserialized scene object data /// FastBufferReader for the NetworkVariable data /// NetworkManager instance - /// optional to use NetworkObject deserialized +#if NGO_DAMODE + /// will be true if invoked by CreateObjectMessage + /// The deserialized NetworkObject or null if deserialization failed + internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false) +#else + /// The deserialized NetworkObject or null if deserialization failed internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager) +#endif { //Attempt to create a local NetworkObject var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject); @@ -1893,6 +3055,57 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf // Spawn the NetworkObject networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene); +#if NGO_DAMODE + if (sceneObject.SyncObservers) + { + foreach (var observer in sceneObject.Observers) + { + networkObject.Observers.Add(observer); + } + } + + if (networkManager.DistributedAuthorityMode) + { + networkObject.SpawnWithObservers = sceneObject.SpawnWithObservers; + } + + // If this was not invoked by a message handler, we are in distributed authority mode, and we are spawning with observers or + // we are an observer (in case SpawnWithObservers is false) + if (networkManager.DistributedAuthorityMode && (!invokedByMessage || networkObject.IsPlayerObject) && + (networkObject.SpawnWithObservers || networkObject.Observers.Contains(networkManager.LocalClientId))) + { + if (networkManager.LocalClient != null && networkManager.LocalClient.PlayerObject != null) + { + var playerObject = networkManager.LocalClient.PlayerObject; + if (networkObject.IsPlayerObject) + { + // If it is another player, then make sure the local player is aware of the player + playerObject.Observers.Add(networkObject.OwnerClientId); + } + + // Assure the local player has observability + networkObject.Observers.Add(playerObject.OwnerClientId); + + // If it is a player object, then add it to all known spawned NetworkObjects that spawn with observers + if (networkObject.IsPlayerObject) + { + foreach (var netObject in networkManager.SpawnManager.SpawnedObjects) + { + if (netObject.Value.SpawnWithObservers) + { + netObject.Value.Observers.Add(networkObject.OwnerClientId); + } + } + } + + // Add all known players to the observers list if they don't already exist + foreach (var player in networkManager.SpawnManager.PlayerObjects) + { + networkObject.Observers.Add(player.OwnerClientId); + } + } + } +#endif return networkObject; } @@ -1947,22 +3160,43 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) internal void SceneChangedUpdate(Scene scene, bool notify = false) { // Avoiding edge case scenarios, if no NetworkSceneManager exit early - if (NetworkManager.SceneManager == null) + if (NetworkManager.SceneManager == null || !IsSpawned) + { + return; + } + +#if NGO_DAMODE + if (NetworkManager.SceneManager.IsSceneEventInProgress()) { return; } + var isAuthority = HasAuthority; +#else + var isAuthority = NetworkManager.IsServer; +#endif SceneOriginHandle = scene.handle; - // Clients need to update the NetworkSceneHandle - if (!NetworkManager.IsServer && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) + + // non-authority needs to update the NetworkSceneHandle + if (!isAuthority && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) { NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } - else if (NetworkManager.IsServer) + else if (isAuthority) { - // Since the server is the source of truth for the NetworkSceneHandle, + // Since the authority is the source of truth for the NetworkSceneHandle, // the NetworkSceneHandle is the same as the SceneOriginHandle. - NetworkSceneHandle = SceneOriginHandle; +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; + } + else +#endif + { + NetworkSceneHandle = SceneOriginHandle; + } + } else // Otherwise, the client did not find the client to server scene handle if (NetworkManager.LogLevel == LogLevel.Developer) @@ -1977,8 +3211,8 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } OnMigratedToNewScene?.Invoke(); - // Only the server side will notify clients of non-parented NetworkObject scene changes - if (NetworkManager.IsServer && notify && transform.parent == null) + // Only the authority side will notify clients of non-parented NetworkObject scene changes + if (isAuthority && notify && transform.parent == null) { NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this); } @@ -2000,7 +3234,7 @@ private void Update() // Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle - if (!SceneMigrationSynchronization || NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned + if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager == null || NetworkManager.ShutdownInProgress || IsSceneObject != false || gameObject.scene.handle == SceneOriginHandle) { return; @@ -2066,7 +3300,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) { if (networkBehaviour.IsSpawned && IsSpawned) { - if (NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkManager?.LogLevel == LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index c8160f0370..11fe3df957 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -39,6 +39,14 @@ public static class NetworkLog /// The message to log public static void LogInfoServer(string message) => LogServer(message, LogType.Info); +#if NGO_DAMODE + /// + /// Logs an info log locally and on the session owner if possible. + /// + /// The message to log + public static void LogInfoSessionOwner(string message) => LogServer(message, LogType.Info); +#endif + /// /// Logs a warning log locally and on the server if possible. /// @@ -58,7 +66,12 @@ private static void LogServer(string message, LogType logType) var networkManager = NetworkManagerOverride ??= NetworkManager.Singleton; // Get the sender of the local log ulong localId = networkManager?.LocalClientId ?? 0; +#if NGO_DAMODE + bool isServer = networkManager && networkManager.DistributedAuthorityMode ? networkManager.LocalClient.IsSessionOwner : + networkManager && !networkManager.DistributedAuthorityMode ? networkManager.IsServer : true; +#else bool isServer = networkManager?.IsServer ?? true; +#endif switch (logType) { case LogType.Info: @@ -98,7 +111,10 @@ private static void LogServer(string message, LogType logType) var networkMessage = new ServerLogMessage { LogType = logType, - Message = message + Message = message, +#if NGO_DAMODE + SenderId = localId +#endif }; var size = networkManager.ConnectionManager.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId); @@ -106,9 +122,28 @@ private static void LogServer(string message, LogType logType) } } +#if NGO_DAMODE + private static string Header() + { + var networkManager = NetworkManagerOverride ??= NetworkManager.Singleton; + if (networkManager.DistributedAuthorityMode) + { + return "Session-Owner"; + } + return "Netcode-Server"; + } + + internal static void LogInfoServerLocal(string message, ulong sender) => Debug.Log($"[{Header()} Sender={sender}] {message}"); + internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[{Header()} Sender={sender}] {message}"); + internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[{Header()} Sender={sender}] {message}"); + +#else internal static void LogInfoServerLocal(string message, ulong sender) => Debug.Log($"[Netcode-Server Sender={sender}] {message}"); internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[Netcode-Server Sender={sender}] {message}"); internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[Netcode-Server Sender={sender}] {message}"); +#endif + + internal enum LogType : byte { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index 620e5bb48b..1b8626cd3e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -152,18 +152,14 @@ internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader read // We dont know what size to use. Try every (more collision prone) if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) { - // handler can remove itself, cache the name for metrics - string messageName = m_MessageHandlerNameLookup32[hash]; messageHandler32(sender, reader); - m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount); + m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); } if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) { - // handler can remove itself, cache the name for metrics - string messageName = m_MessageHandlerNameLookup64[hash]; messageHandler64(sender, reader); - m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount); + m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); } } else @@ -174,19 +170,15 @@ internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader read case HashSize.VarIntFourBytes: if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) { - // handler can remove itself, cache the name for metrics - string messageName = m_MessageHandlerNameLookup32[hash]; messageHandler32(sender, reader); - m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount); + m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); } break; case HashSize.VarIntEightBytes: if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) { - // handler can remove itself, cache the name for metrics - string messageName = m_MessageHandlerNameLookup64[hash]; messageHandler64(sender, reader); - m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount); + m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); } break; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs index 728f0a7f64..71b43fdd0d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs @@ -15,6 +15,9 @@ protected struct TriggerData } protected struct TriggerInfo { +#if NGO_DAMODE + public string MessageType; +#endif public float Expiry; public NativeList TriggerData; } @@ -36,7 +39,11 @@ internal DeferredMessageManager(NetworkManager networkManager) /// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed /// without the requested object ID being spawned, the triggers for it are automatically deleted. /// +#if NGO_DAMODE + public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType) +#else public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context) +#endif { if (!m_Triggers.TryGetValue(trigger, out var triggers)) { @@ -48,6 +55,9 @@ public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerTy { triggerInfo = new TriggerInfo { +#if NGO_DAMODE + MessageType = messageType, +#endif Expiry = m_NetworkManager.RealTimeProvider.RealTimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout, TriggerData = new NativeList(Allocator.Persistent) }; @@ -90,11 +100,40 @@ public virtual unsafe void CleanupStaleTriggers() } } +#if NGO_DAMODE + /// + /// Used for testing purposes + /// + internal static bool IncludeMessageType = true; + + private string GetWarningMessage(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo, float spawnTimeout) + { + if (IncludeMessageType) + { + return $"[Deferred {triggerType}] Messages were received for a trigger of type {triggerInfo.MessageType} associated with id ({key}), but the {nameof(NetworkObject)} was not received within the timeout period {spawnTimeout} second(s)."; + } + else + { + return $"Deferred messages were received for a trigger of type {triggerType} associated with id ({key}), but the {nameof(NetworkObject)} was not received within the timeout period {spawnTimeout} second(s)."; + } + } +#else + private string GetWarningMessage(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo, float spawnTimeout) + { + return $"Deferred messages were received for a trigger of type {triggerType} associated with id ({key}), but the {nameof(NetworkObject)} was not received within the timeout period {spawnTimeout} second(s)."; + } +#endif + protected virtual void PurgeTrigger(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo) { +#if NGO_DAMODE + var logLevel = m_NetworkManager.DistributedAuthorityMode ? LogLevel.Developer : LogLevel.Normal; + if (NetworkLog.CurrentLogLevel <= logLevel) +#else if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) +#endif { - NetworkLog.LogWarning($"Deferred messages were received for a trigger of type {triggerType} with key {key}, but that trigger was not received within within {m_NetworkManager.NetworkConfig.SpawnTimeout} second(s)."); + NetworkLog.LogWarning(GetWarningMessage(triggerType, key, triggerInfo, m_NetworkManager.NetworkConfig.SpawnTimeout)); } foreach (var data in triggerInfo.TriggerData) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs index 0a9ec917f3..6bdd11bc58 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs @@ -1,3 +1,4 @@ + namespace Unity.Netcode { internal interface IDeferredNetworkMessageManager @@ -17,7 +18,11 @@ internal enum TriggerType /// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed /// without the requested object ID being spawned, the triggers for it are automatically deleted. /// +#if NGO_DAMODE + void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType = null); +#else void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context); +#endif /// /// Cleans up any trigger that's existed for more than a second. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index b0d1ca7c4d..225a20cb76 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -1,3 +1,4 @@ + namespace Unity.Netcode { internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy @@ -7,10 +8,149 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem public ulong NetworkObjectId; public ulong OwnerClientId; +#if NGO_DAMODE + // DA-NGO CMB SERVICE NOTES: + // When forwarding the message to clients on the CMB Service side, + // you can set the ClientIdCount to 0 and skip writing the ClientIds. + // See the NetworkObjet.OwnershipRequest for more potential service side additions + + /// + /// When requesting, RequestClientId is the requestor. + /// When approving, RequestClientId is the owner that approved. + /// When responding (only for denied), RequestClientId is the requestor + /// + internal ulong RequestClientId; + internal int ClientIdCount; + internal ulong[] ClientIds; + internal bool DistributedAuthorityMode; + internal ushort OwnershipFlags; + internal byte OwnershipRequestResponseStatus; + private byte m_OwnershipMessageTypeFlags; + + private const byte k_OwnershipChanging = 0x01; + private const byte k_OwnershipFlagsUpdate = 0x02; + private const byte k_RequestOwnership = 0x04; + private const byte k_RequestApproved = 0x08; + private const byte k_RequestDenied = 0x10; + + // If no flags are set, then ownership is changing + internal bool OwnershipIsChanging + { + get + { + return GetFlag(k_OwnershipChanging); + } + + set + { + SetFlag(value, k_OwnershipChanging); + } + } + + internal bool OwnershipFlagsUpdate + { + get + { + return GetFlag(k_OwnershipFlagsUpdate); + } + + set + { + SetFlag(value, k_OwnershipFlagsUpdate); + } + } + + internal bool RequestOwnership + { + get + { + return GetFlag(k_RequestOwnership); + } + + set + { + SetFlag(value, k_RequestOwnership); + } + } + + internal bool RequestApproved + { + get + { + return GetFlag(k_RequestApproved); + } + + set + { + SetFlag(value, k_RequestApproved); + } + } + + internal bool RequestDenied + { + get + { + return GetFlag(k_RequestDenied); + } + + set + { + SetFlag(value, k_RequestDenied); + } + } + + private bool GetFlag(int flag) + { + return (m_OwnershipMessageTypeFlags & flag) != 0; + } + + private void SetFlag(bool set, byte flag) + { + if (set) { m_OwnershipMessageTypeFlags = (byte)(m_OwnershipMessageTypeFlags | flag); } + else { m_OwnershipMessageTypeFlags = (byte)(m_OwnershipMessageTypeFlags & ~flag); } + } +#endif + public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, OwnerClientId); +#if NGO_DAMODE + if (DistributedAuthorityMode) + { + BytePacker.WriteValueBitPacked(writer, ClientIdCount); + if (ClientIdCount > 0) + { + if (ClientIdCount != ClientIds.Length) + { + throw new System.Exception($"[{nameof(ChangeOwnershipMessage)}] ClientIdCount is {ClientIdCount} but the ClientIds length is {ClientIds.Length}!"); + } + foreach (var clientId in ClientIds) + { + BytePacker.WriteValueBitPacked(writer, clientId); + } + } + + writer.WriteValueSafe(m_OwnershipMessageTypeFlags); + if (OwnershipFlagsUpdate || OwnershipIsChanging) + { + writer.WriteValueSafe(OwnershipFlags); + } + + // When requesting, it is the requestor + // When approving, it is the owner that approved + // When denied, it is the requestor + if (RequestOwnership || RequestApproved || RequestDenied) + { + writer.WriteValueSafe(RequestClientId); + + if (RequestDenied) + { + writer.WriteValueSafe(OwnershipRequestResponseStatus); + } + } + } +#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) @@ -22,23 +162,146 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); + +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + ByteUnpacker.ReadValueBitPacked(reader, out ClientIdCount); + if (ClientIdCount > 0) + { + ClientIds = new ulong[ClientIdCount]; + var clientId = (ulong)0; + for (int i = 0; i < ClientIdCount; i++) + { + ByteUnpacker.ReadValueBitPacked(reader, out clientId); + ClientIds[i] = clientId; + } + } + + reader.ReadValueSafe(out m_OwnershipMessageTypeFlags); + if (OwnershipFlagsUpdate || OwnershipIsChanging) + { + reader.ReadValueSafe(out OwnershipFlags); + } + + // When requesting, it is the requestor + // When approving, it is the owner that approved + // When denied, it is the requestor + if (RequestOwnership || RequestApproved || RequestDenied) + { + reader.ReadValueSafe(out RequestClientId); + + if (RequestDenied) + { + reader.ReadValueSafe(out OwnershipRequestResponseStatus); + } + } + } + + // If we are not a DAHost instance and the NetworkObject does not exist then defer it as it very likely is not spawned yet. + // Otherwise if we are the DAHost and it does not exist then we want to forward this message because when the NetworkObject + // is made visible again, the ownership flags and owner information will be synchronized with the DAHost by the current + // authority of the NetworkObject in question. + if (!networkManager.DAHost && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); +#else if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); +#endif return false; } - return true; } public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; + +#if NGO_DAMODE + // If we are the DAHost then forward this message + if (networkManager.DAHost) + { + var clientList = ClientIdCount > 0 ? ClientIds : networkManager.ConnectedClientsIds; + + var message = new ChangeOwnershipMessage() + { + NetworkObjectId = NetworkObjectId, + OwnerClientId = OwnerClientId, + DistributedAuthorityMode = true, + OwnershipFlags = OwnershipFlags, + RequestClientId = RequestClientId, + ClientIdCount = 0, + m_OwnershipMessageTypeFlags = m_OwnershipMessageTypeFlags, + }; + + if (RequestDenied) + { + // If the local DAHost's client is not the target, then forward to the target + if (RequestClientId != networkManager.LocalClientId) + { + message.OwnershipRequestResponseStatus = OwnershipRequestResponseStatus; + networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, RequestClientId); + // We don't want the local DAHost's client to process this message, so exit early + return; + } + } + else if (RequestOwnership) + { + // If the DAHost client is not authority, just forward the message to the authority + if (OwnerClientId != networkManager.LocalClientId) + { + networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, OwnerClientId); + // We don't want the local DAHost's client to process this message, so exit early + return; + } + // Otherwise, fall through and process the request. + } + else + { + foreach (var clientId in clientList) + { + if (clientId == networkManager.LocalClientId) + { + continue; + } + + // If ownership is changing and this is not an ownership request approval then ignore the OnwerClientId + // If it is just updating flags then ignore sending to the owner + // If it is a request or approving request, then ignore the RequestClientId + if ((OwnershipIsChanging && !RequestApproved && OwnerClientId == clientId) || (OwnershipFlagsUpdate && clientId == OwnerClientId) + || ((RequestOwnership || RequestApproved) && clientId == RequestClientId)) + { + continue; + } + + networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, clientId); + } + } + // If the NetworkObject is not visible to the DAHost client, then exit early + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) + { + return; + } + } + + // If ownership is changing, then run through the ownershipd changed sequence + // Note: There is some extended ownership script at the bottom of HandleOwnershipChange + // If not in distributed authority mode, then always go straight to HandleOwnershipChange + if (OwnershipIsChanging || !networkManager.DistributedAuthorityMode) + { + HandleOwnershipChange(ref context); + } + else if (networkManager.DistributedAuthorityMode) + { + // Otherwise, we handle and extended ownership update + HandleExtendedOwnershipUpdate(ref context); + } +#else var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; var originalOwner = networkObject.OwnerClientId; - networkObject.OwnerClientId = OwnerClientId; - // We are current owner. if (originalOwner == networkManager.LocalClientId) { @@ -61,8 +324,119 @@ public void Handle(ref NetworkContext context) } networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId); + networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); +#endif + } + +#if NGO_DAMODE + + /// + /// Handle the + /// + /// + private void HandleExtendedOwnershipUpdate(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + + // Handle the extended ownership message types + var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; + + if (OwnershipFlagsUpdate) + { + // Just update the ownership flags + networkObject.Ownership = (NetworkObject.OwnershipStatus)OwnershipFlags; + } + else if (RequestOwnership) + { + // Requesting ownership, if allowed it will automatically send the ownership change message + networkObject.OwnershipRequest(RequestClientId); + } + else if (RequestDenied) + { + networkObject.OwnershipRequestResponse((NetworkObject.OwnershipRequestResponseStatus)OwnershipRequestResponseStatus); + } + } + + /// + /// Handle the traditional change in ownership message type logic + /// + /// + private void HandleOwnershipChange(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; + + // DANGO-TODO: This probably shouldn't be allowed to happen. + if (networkObject.OwnerClientId == OwnerClientId) + { + UnityEngine.Debug.LogWarning($"Unnecessary ownership changed message for {NetworkObjectId}"); + } + + var originalOwner = networkObject.OwnerClientId; + networkObject.OwnerClientId = OwnerClientId; + + if (networkManager.DistributedAuthorityMode) + { + networkObject.Ownership = (NetworkObject.OwnershipStatus)OwnershipFlags; + } + + // We are current owner (client-server) or running in distributed authority mode + if (originalOwner == networkManager.LocalClientId || networkManager.DistributedAuthorityMode) + { + networkObject.InvokeBehaviourOnLostOwnership(); + } + + // We are new owner or (client-server) or running in distributed authority mode + if (OwnerClientId == networkManager.LocalClientId || networkManager.DistributedAuthorityMode) + { + networkObject.InvokeBehaviourOnGainedOwnership(); + } + + // If in distributed authority mode + if (networkManager.DistributedAuthorityMode) + { + // Always update the network properties in distributed authority mode + for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) + { + networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); + } + } + else // Otherwise update properties like we would in client-server + { + // For all other clients that are neither the former or current owner, update the behaviours' properties + if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId) + { + for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) + { + networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); + } + } + } + + // Always invoke ownership change notifications + networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId); + + // If this change was requested, then notify that the request was approved (doing this last so all ownership + // changes have already been applied if the callback is invoked) + if (networkManager.DistributedAuthorityMode && networkManager.LocalClientId == OwnerClientId) + { + if (RequestApproved) + { + networkObject.OwnershipRequestResponse(NetworkObject.OwnershipRequestResponseStatus.Approved); + } + + // If the NetworkObject changed ownership and the Requested flag was set (i.e. it was an ownership request), + // then the new owner granted ownership removes the Requested flag and sends out an ownership status update. + if (networkObject.HasExtendedOwnershipStatus(NetworkObject.OwnershipStatusExtended.Requested)) + { + networkObject.RemoveOwnershipExtended(NetworkObject.OwnershipStatusExtended.Requested); + networkObject.SendOwnershipStatusUpdate(); + } + } networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); } +#endif + } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index 92ed2eb298..aa5f66835c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -6,9 +6,17 @@ internal struct ClientConnectedMessage : INetworkMessage, INetworkSerializeByMem public ulong ClientId; +#if NGO_DAMODE + public bool ShouldSynchronize; +#endif + + public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, ClientId); +#if NGO_DAMODE + writer.WriteValueSafe(ShouldSynchronize); +#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) @@ -19,17 +27,50 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return false; } ByteUnpacker.ReadValueBitPacked(reader, out ClientId); +#if NGO_DAMODE + reader.ReadValueSafe(out ShouldSynchronize); +#endif + return true; } public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId); +#if NGO_DAMODE + if ((ShouldSynchronize || networkManager.CMBServiceConnection) && networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) + { + networkManager.SceneManager.SynchronizeNetworkObjects(ClientId); + } + else + { + // All modes support adding NetworkClients + networkManager.ConnectionManager.AddClient(ClientId); + } +#endif + if (!networkManager.ConnectionManager.ConnectedClientIds.Contains(ClientId)) + { + networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId); + } if (networkManager.IsConnectedClient) { networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId); } + +#if NGO_DAMODE + // DANGO-TODO: Remove the session owner object distribution check once the service handles object distribution + if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && !networkManager.NetworkConfig.EnableSceneManagement) + { + // Don't redistribute for the local instance + if (ClientId != networkManager.LocalClientId) + { + // We defer redistribution to the end of the NetworkUpdateStage.PostLateUpdate + networkManager.RedistributeToClient = true; + networkManager.ClientToRedistribute = ClientId; + networkManager.TickToRedistribute = networkManager.ServerTime.Tick + 20; + } + } +#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs index 9d306cbb56..1a863ce9e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs @@ -25,6 +25,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; +#if NGO_DAMODE + // All modes support removing NetworkClients + networkManager.ConnectionManager.RemoveClient(ClientId); +#endif networkManager.ConnectionManager.ConnectedClientIds.Remove(ClientId); if (networkManager.IsConnectedClient) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 5635f2369f..c2295f2a45 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -10,6 +10,13 @@ internal struct ConnectionApprovedMessage : INetworkMessage public ulong OwnerClientId; public int NetworkTick; +#if NGO_DAMODE + // The cloud state service should set this if we are restoring a session + public bool IsRestoredSession; + public ulong CurrentSessionOwner; + // Not serialized + public bool IsDistributedAuthority; +#endif // Not serialized, held as references to serialize NetworkVariable data public HashSet SpawnedObjectsList; @@ -38,6 +45,13 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, OwnerClientId); BytePacker.WriteValueBitPacked(writer, NetworkTick); +#if NGO_DAMODE + if (IsDistributedAuthority) + { + writer.WriteValueSafe(IsRestoredSession); + BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner); + } +#endif if (targetVersion >= k_VersionAddClientIds) { @@ -59,7 +73,12 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))) { sobj.Observers.Add(OwnerClientId); +#if NGO_DAMODE + // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. + var sceneObject = sobj.GetMessageSceneObject(OwnerClientId, IsDistributedAuthority); +#else var sceneObject = sobj.GetMessageSceneObject(OwnerClientId); +#endif sceneObject.Serialize(writer); ++sceneObjectCount; } @@ -114,6 +133,13 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick); +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + reader.ReadValueSafe(out IsRestoredSession); + ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner); + } +#endif if (receivedMessageVersion >= k_VersionAddClientIds) { @@ -131,9 +157,23 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"[Client-{OwnerClientId}] Connection approved! Synchronizing..."); + } networkManager.LocalClientId = OwnerClientId; networkManager.MessageManager.SetLocalClientId(networkManager.LocalClientId); networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId); +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + networkManager.SetSessionOwner(CurrentSessionOwner); + if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement) + { + networkManager.SceneManager.InitializeScenesLoaded(); + } + } +#endif var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick); networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport. @@ -148,13 +188,30 @@ public void Handle(ref NetworkContext context) networkManager.ConnectionManager.ConnectedClientIds.Clear(); foreach (var clientId in ConnectedClientIds) { - networkManager.ConnectionManager.ConnectedClientIds.Add(clientId); + if (!networkManager.ConnectionManager.ConnectedClientIds.Contains(clientId)) + { + networkManager.ConnectionManager.AddClient(clientId); + } } // Only if scene management is disabled do we handle NetworkObject synchronization at this point if (!networkManager.NetworkConfig.EnableSceneManagement) { +#if NGO_DAMODE + // DANGO-TODO: This is a temporary fix for no DA CMB scene event handling. + // We will either use this same concept or provide some way for the CMB state plugin to handle it. + if (networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) + { + networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + } + else + { + networkManager.SpawnManager.DestroySceneObjects(); + } +#else networkManager.SpawnManager.DestroySceneObjects(); +#endif + m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount); // Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing @@ -168,9 +225,43 @@ public void Handle(ref NetworkContext context) // Mark the client being connected networkManager.IsConnectedClient = true; + +#if NGO_DAMODE + if (networkManager.AutoSpawnPlayerPrefabClientSide) + { + networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId); + } +#endif + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"[Client-{OwnerClientId}][Scene Management Disabled] Synchronization complete!"); + } // When scene management is disabled we notify after everything is synchronized networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); } + else + { +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement) + { + // Mark the client being connected + networkManager.IsConnectedClient = true; + + // Spawn any in-scene placed NetworkObjects + networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + + // Spawn the local player of the session owner + if (networkManager.AutoSpawnPlayerPrefabClientSide) + { + networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId); + } + // Synchronize the service with the initial session owner's loaded scenes and spawned objects + networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); + } +#endif + } + + ConnectedClientIds.Dispose(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index 8d558bf202..a7457daa32 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -8,6 +8,12 @@ internal struct ConnectionRequestMessage : INetworkMessage public ulong ConfigHash; +#if NGO_DAMODE + public bool CMBServiceConnection; + public uint TickRate; + public bool EnableSceneManagement; +#endif + public byte[] ConnectionData; public bool ShouldSendConnectionData; @@ -30,6 +36,13 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // END FORBIDDEN SEGMENT // ============================================================ +#if NGO_DAMODE + if (CMBServiceConnection) + { + writer.WriteValueSafe(TickRate); + writer.WriteValueSafe(EnableSceneManagement); + } +#endif if (ShouldSendConnectionData) { writer.WriteValueSafe(ConfigHash); @@ -148,11 +161,19 @@ public void Handle(ref NetworkContext context) } else { +#if NGO_DAMODE + var response = new NetworkManager.ConnectionApprovalResponse + { + Approved = true, + CreatePlayerObject = networkManager.DistributedAuthorityMode && networkManager.AutoSpawnPlayerPrefabClientSide ? false : networkManager.NetworkConfig.PlayerPrefab != null + }; +#else var response = new NetworkManager.ConnectionApprovalResponse { Approved = true, CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null }; +#endif networkManager.ConnectionManager.HandleConnectionApproval(senderId, response); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 809b7c92f4..e94f711135 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -1,4 +1,6 @@ +using System.Linq; using System.Runtime.CompilerServices; + namespace Unity.Netcode { internal struct CreateObjectMessage : INetworkMessage @@ -8,9 +10,115 @@ internal struct CreateObjectMessage : INetworkMessage public NetworkObject.SceneObject ObjectInfo; private FastBufferReader m_ReceivedNetworkVariableData; +#if NGO_DAMODE + // DA - NGO CMB SERVICE NOTES: + // The ObserverIds and ExistingObserverIds will only be populated if k_UpdateObservers is set + // ObserverIds is the full list of observers (see below) + internal ulong[] ObserverIds; + + // While this does consume a bit more bandwidth, this is only sent by the authority/owner + // and can be used to determine which clients should receive the ObjectInfo serialized data. + // All other already existing observers just need to receive the NewObserverIds and the + // NetworkObjectId + internal ulong[] NewObserverIds; + + // If !IncludesSerializedObject then the NetworkObjectId will be serialized. + // This happens when we are just sending an update to the observers list + // to clients that already have the NetworkObject spawned + internal ulong NetworkObjectId; + + private const byte k_IncludesSerializedObject = 0x01; + private const byte k_UpdateObservers = 0x02; + private const byte k_UpdateNewObservers = 0x04; + + + private byte m_CreateObjectMessageTypeFlags; + + internal bool IncludesSerializedObject + { + get + { + return GetFlag(k_IncludesSerializedObject); + } + + set + { + SetFlag(value, k_IncludesSerializedObject); + } + } + + internal bool UpdateObservers + { + get + { + return GetFlag(k_UpdateObservers); + } + + set + { + SetFlag(value, k_UpdateObservers); + } + } + + internal bool UpdateNewObservers + { + get + { + return GetFlag(k_UpdateNewObservers); + } + + set + { + SetFlag(value, k_UpdateNewObservers); + } + } + + private bool GetFlag(int flag) + { + return (m_CreateObjectMessageTypeFlags & flag) != 0; + } + + private void SetFlag(bool set, byte flag) + { + if (set) { m_CreateObjectMessageTypeFlags = (byte)(m_CreateObjectMessageTypeFlags | flag); } + else { m_CreateObjectMessageTypeFlags = (byte)(m_CreateObjectMessageTypeFlags & ~flag); } + } +#endif + public void Serialize(FastBufferWriter writer, int targetVersion) { +#if NGO_DAMODE + writer.WriteValueSafe(m_CreateObjectMessageTypeFlags); + + if (UpdateObservers) + { + BytePacker.WriteValuePacked(writer, ObserverIds.Length); + foreach (var clientId in ObserverIds) + { + BytePacker.WriteValuePacked(writer, clientId); + } + } + + if (UpdateNewObservers) + { + BytePacker.WriteValuePacked(writer, NewObserverIds.Length); + foreach (var clientId in NewObserverIds) + { + BytePacker.WriteValuePacked(writer, clientId); + } + } + + if (IncludesSerializedObject) + { + ObjectInfo.Serialize(writer); + } + else + { + BytePacker.WriteValuePacked(writer, NetworkObjectId); + } +#else ObjectInfo.Serialize(writer); +#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) @@ -21,6 +129,54 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return false; } +#if NGO_DAMODE + reader.ReadValueSafe(out m_CreateObjectMessageTypeFlags); + if (UpdateObservers) + { + var length = 0; + ByteUnpacker.ReadValuePacked(reader, out length); + ObserverIds = new ulong[length]; + var clientId = (ulong)0; + for (int i = 0; i < length; i++) + { + ByteUnpacker.ReadValuePacked(reader, out clientId); + ObserverIds[i] = clientId; + } + } + + if (UpdateNewObservers) + { + var length = 0; + ByteUnpacker.ReadValuePacked(reader, out length); + NewObserverIds = new ulong[length]; + var clientId = (ulong)0; + for (int i = 0; i < length; i++) + { + ByteUnpacker.ReadValuePacked(reader, out clientId); + NewObserverIds[i] = clientId; + } + } + + if (IncludesSerializedObject) + { + ObjectInfo.Deserialize(reader); + } + else + { + ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId); + } + + if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo)) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context, GetType().Name); + return false; + } + m_ReceivedNetworkVariableData = reader; +#else + if (!networkManager.IsClient) + { + return false; + } ObjectInfo.Deserialize(reader); if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo)) { @@ -28,6 +184,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return false; } m_ReceivedNetworkVariableData = reader; +#endif return true; } @@ -38,21 +195,150 @@ public void Handle(ref NetworkContext context) // If a client receives a create object message and it is still synchronizing, then defer the object creation until it has finished synchronizing if (networkManager.SceneManager.ShouldDeferCreateObject()) { +#if NGO_DAMODE + networkManager.SceneManager.DeferCreateObject(context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData, ObserverIds, NewObserverIds); +#else networkManager.SceneManager.DeferCreateObject(context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData); +#endif } else { +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode && !IncludesSerializedObject && UpdateObservers) + { + ObjectInfo = new NetworkObject.SceneObject() + { + NetworkObjectId = NetworkObjectId, + }; + } + CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData, ObserverIds, NewObserverIds); +#else CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData); +#endif } } + +#if NGO_DAMODE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void CreateObject(ref NetworkManager networkManager, ref NetworkSceneManager.DeferredObjectCreation deferredObjectCreation) + { + var senderId = deferredObjectCreation.SenderId; + var observerIds = deferredObjectCreation.ObserverIds; + var newObserverIds = deferredObjectCreation.NewObserverIds; + var messageSize = deferredObjectCreation.MessageSize; + var sceneObject = deferredObjectCreation.SceneObject; + var networkVariableData = deferredObjectCreation.FastBufferReader; + CreateObject(ref networkManager, senderId, messageSize, sceneObject, networkVariableData, observerIds, newObserverIds); + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NGO_DAMODE + internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData, ulong[] observerIds, ulong[] newObserverIds) +#else internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData) +#endif { + var networkObject = (NetworkObject)null; try { - var networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager); - networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize); +#if NGO_DAMODE + if (!networkManager.DistributedAuthorityMode) + { + networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager); + } + else + { + var hasObserverIdList = observerIds != null && observerIds.Length > 0; + var hasNewObserverIdList = newObserverIds != null && newObserverIds.Length > 0; + // Depending upon visibility of the NetworkObject and the client in question, it could be that + // this client already has visibility of this NetworkObject + if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(sceneObject.NetworkObjectId)) + { + // If so, then just get the local instance + networkObject = networkManager.SpawnManager.SpawnedObjects[sceneObject.NetworkObjectId]; + + // This should not happen, logging error just in case + if (hasNewObserverIdList && newObserverIds.Contains(networkManager.LocalClientId)) + { + NetworkLog.LogErrorServer($"[{nameof(CreateObjectMessage)}][Duplicate-Broadcast] Detected duplicated object creation for {sceneObject.NetworkObjectId}!"); + } + else // Trap to make sure the owner is not receiving any messages it sent + if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId) + { + NetworkLog.LogWarning($"[{nameof(CreateObjectMessage)}][Client-{networkManager.LocalClientId}][Duplicate-CreateObjectMessage][Client Is Owner] Detected duplicated object creation for {networkObject.name}-{sceneObject.NetworkObjectId}!"); + } + } + else + { + networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager, true); + } + + // DA - NGO CMB SERVICE NOTES: + // It is possible for two clients to connect at the exact same time which, due to client-side spawning, can cause each client + // to miss their spawns. For now, all player NetworkObject spawns will always be visible to all known connected clients + var clientList = hasObserverIdList && !networkObject.IsPlayerObject ? observerIds : networkManager.ConnectedClientsIds; + + // Update the observers for this instance + foreach (var clientId in clientList) + { + networkObject.Observers.Add(clientId); + } + + // Mock CMB Service and forward to all clients + if (networkManager.DAHost) + { + // DA - NGO CMB SERVICE NOTES: + // (*** See above notes fist ***) + // If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively + // the same frame), then we need to check the observers list to make sure all players are visible upon first spawning. At a later date, + // for area of interest we will need to have some form of follow up "observer update" message to cull out players not within each + // player's AOI. + if (networkObject.IsPlayerObject && hasNewObserverIdList && clientList.Count != observerIds.Length) + { + // For same-frame newly spawned players that might not be aware of all other players, update the player's observer + // list. + observerIds = clientList.ToArray(); + } + + var createObjectMessage = new CreateObjectMessage() + { + ObjectInfo = sceneObject, + m_ReceivedNetworkVariableData = networkVariableData, + ObserverIds = hasObserverIdList ? observerIds : null, + NetworkObjectId = networkObject.NetworkObjectId, + IncludesSerializedObject = true, + }; + foreach (var clientId in clientList) + { + // DA - NGO CMB SERVICE NOTES: + // If the authority did not specify the list of clients and the client is not an observer or we are the owner/originator + // or we are the DAHost, then we skip sending the message. + if ((!hasObserverIdList && (!networkObject.Observers.Contains(clientId)) || + clientId == networkObject.OwnerClientId || clientId == NetworkManager.ServerClientId)) + { + continue; + } + + // DA - NGO CMB SERVICE NOTES: + // If this included a list of new observers and the targeted clientId is one of the observers, then send the serialized data. + // Otherwise, the targeted clientId has already has visibility (i.e. it is already spawned) and so just send the updated + // observers list to that client's instance. + createObjectMessage.IncludesSerializedObject = hasNewObserverIdList && newObserverIds.Contains(clientId); + + networkManager.SpawnManager.SendSpawnCallForObject(clientId, networkObject); + } + } + } +#else + networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager); +#endif + if (networkObject != null) + { + networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize); + } } catch (System.Exception ex) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index b02b42c8e5..b3d1d08454 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -1,3 +1,5 @@ +using System.Linq; + namespace Unity.Netcode { internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy @@ -6,10 +8,57 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp public ulong NetworkObjectId; public bool DestroyGameObject; +#if NGO_DAMODE + private byte m_DestroyFlags; + + internal int DeferredDespawnTick; + // Temporary until we make this a list + internal ulong TargetClientId; + + internal bool IsDistributedAuthority; + + internal const byte ClientTargetedDestroy = 0x01; + + internal bool IsTargetedDestroy + { + get + { + return GetFlag(ClientTargetedDestroy); + } + + set + { + SetFlag(value, ClientTargetedDestroy); + } + } + + private bool GetFlag(int flag) + { + return (m_DestroyFlags & flag) != 0; + } + + private void SetFlag(bool set, byte flag) + { + if (set) { m_DestroyFlags = (byte)(m_DestroyFlags | flag); } + else { m_DestroyFlags = (byte)(m_DestroyFlags & ~flag); } + } +#endif public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, NetworkObjectId); +#if NGO_DAMODE + if (IsDistributedAuthority) + { + writer.WriteByteSafe(m_DestroyFlags); + + if (IsTargetedDestroy) + { + BytePacker.WriteValueBitPacked(writer, TargetClientId); + } + BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); + } +#endif writer.WriteValueSafe(DestroyGameObject); } @@ -22,12 +71,33 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + reader.ReadByteSafe(out m_DestroyFlags); + if (IsTargetedDestroy) + { + ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId); + } + ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); + } +#endif + reader.ReadValueSafe(out DestroyGameObject); if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { +#if NGO_DAMODE + // Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy + if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy)) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); + } +#else networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); return false; +#endif + } return true; } @@ -35,14 +105,87 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) + + var networkObject = (NetworkObject)null; +#if NGO_DAMODE + if (!networkManager.DistributedAuthorityMode) { - // This is the same check and log message that happens inside OnDespawnObject, but we have to do it here - return; + // If this NetworkObject does not exist on this instance then exit early + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject)) + { + return; + } + } + else + { + networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject); + if (!networkManager.DAHost && networkObject == null) + { + // If this NetworkObject does not exist on this instance then exit early + return; + } + } + // DANGO-TODO: This is just a quick way to foward despawn messages to the remaining clients + if (networkManager.DistributedAuthorityMode && networkManager.DAHost) + { + var message = new DestroyObjectMessage + { + NetworkObjectId = NetworkObjectId, + DestroyGameObject = DestroyGameObject, + IsDistributedAuthority = true, + IsTargetedDestroy = IsTargetedDestroy, + TargetClientId = TargetClientId, // Just always populate this value whether we write it or not + DeferredDespawnTick = DeferredDespawnTick, + }; + var ownerClientId = networkObject == null ? context.SenderId : networkObject.OwnerClientId; + var clientIds = networkObject == null ? networkManager.ConnectedClientsIds.ToList() : networkObject.Observers.ToList(); + + foreach (var clientId in clientIds) + { + if (clientId == networkManager.LocalClientId || clientId == ownerClientId) + { + continue; + } + networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + } } - networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize); - networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject); + // If we are deferring the despawn, then add it to the deferred despawn queue + if (networkManager.DistributedAuthorityMode) + { + if (DeferredDespawnTick > 0) + { + // Clients always add it to the queue while DAHost will only add it to the queue if it is not a targeted destroy or it is and the target is the + // DAHost client. + if (!networkManager.DAHost || (networkManager.DAHost && (!IsTargetedDestroy || (IsTargetedDestroy && TargetClientId == 0)))) + { + networkObject.DeferredDespawnTick = DeferredDespawnTick; + var hasCallback = networkObject.OnDeferredDespawnComplete != null; + networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback); + return; + } + } + + // If this is targeted and we are not the target, then just update our local observers for this object + if (IsTargetedDestroy && TargetClientId != networkManager.LocalClientId && networkObject != null) + { + networkObject.Observers.Remove(TargetClientId); + return; + } + } +#else + // If this NetworkObject does not exist on this instance then exit early + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject)) + { + return; + } +#endif + if (networkObject != null) + { + // Otherwise just despawn the NetworkObject right now + networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject); + networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize); + } } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index e151cc7734..22cba216b2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -23,6 +23,8 @@ internal struct NetworkVariableDeltaMessage : INetworkMessage private FastBufferReader m_ReceivedNetworkVariableData; + // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. + // Worth either merging or more cleanly separating these codepaths. public void Serialize(FastBufferWriter writer, int targetVersion) { if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) @@ -35,12 +37,25 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex); +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count); + } +#endif for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++) { if (!DeliveryMappedNetworkVariableIndex.Contains(i)) { +#if NGO_DAMODE // This var does not belong to the currently iterating delivery group. + if (networkManager.DistributedAuthorityMode) + { + writer.WriteValueSafe(0); + } + else +#endif if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { BytePacker.WriteValueBitPacked(writer, (ushort)0); @@ -76,7 +91,16 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { shouldWrite = false; } - +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + if (!shouldWrite) + { + writer.WriteValueSafe(0); + } + } + else +#endif if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (!shouldWrite) @@ -106,7 +130,28 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } else { - networkVariable.WriteDelta(writer); +#if NGO_DAMODE + // DANGO-TODO: + // Complex types with custom type serialization (either registered custom types or INetworkSerializable implementations) will be problematic + // Non-complex types always provide a full state update per delta + // DANGO-TODO: Add NetworkListEvent.EventType awareness to the cloud-state server + if (networkManager.DistributedAuthorityMode) + { + var size_marker = writer.Position; + writer.WriteValueSafe(0); + var start_marker = writer.Position; + networkVariable.WriteDelta(writer); + var end_marker = writer.Position; + writer.Seek(size_marker); + var size = end_marker - start_marker; + writer.WriteValueSafe((ushort)size); + writer.Seek(end_marker); + } + else +#endif + { + networkVariable.WriteDelta(writer); + } } networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( TargetClientId, @@ -128,6 +173,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return true; } + // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. + // Worth either merging or more cleanly separating these codepaths. public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; @@ -145,9 +192,33 @@ public void Handle(ref NetworkContext context) } else { +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableCount); + if (variableCount != networkBehaviour.NetworkVariableFields.Count) + { + UnityEngine.Debug.LogError("Variable count mismatch"); + } + } +#endif + for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++) { int varSize = 0; +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableSize); + varSize = variableSize; + + if (varSize == 0) + { + continue; + } + } + else +#endif if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize); @@ -200,6 +271,7 @@ public void Handle(ref NetworkContext context) } int readStartPos = m_ReceivedNetworkVariableData.Position; + // Read Delta so we also notify any subscribers to a change in the NetworkVariable networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer); networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( @@ -209,7 +281,11 @@ public void Handle(ref NetworkContext context) networkBehaviour.__getTypeName(), context.MessageSize); +#if NGO_DAMODE + if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety || networkManager.DistributedAuthorityMode) +#else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) +#endif { if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize)) { @@ -235,7 +311,14 @@ public void Handle(ref NetworkContext context) } else { + // DANGO-TODO: Fix me! + // When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to + // log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring. +#if NGO_DAMODE + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, GetType().Name); +#else networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context); +#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index abbe88802e..7859ab065b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -33,6 +33,14 @@ public bool RemoveParent set => ByteUtility.SetBit(ref m_BitField, 2, value); } +#if NGO_DAMODE + public bool AuthorityApplied + { + get => ByteUtility.GetBit(m_BitField, 3); + set => ByteUtility.SetBit(ref m_BitField, 3, value); + } +#endif + // These additional properties are used to synchronize clients with the current position, // rotation, and scale after parenting/de-parenting (world/local space relative). This // allows users to control the final child's transform values without having to have a @@ -83,9 +91,16 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int reader.ReadValueSafe(out Rotation); reader.ReadValueSafe(out Scale); +#if NGO_DAMODE + // If the target NetworkObject does not exist =or= the target latest parent does not exist then defer the message + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId) || (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value))) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); +#else if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); +#endif return false; } return true; @@ -95,6 +110,17 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; + +#if NGO_DAMODE + // For either DA or Client-Server modes, parenting is only valid if the parent was owned by a different authority (i.e. AuthorityApplied) or the sender is from the owner (DA mode) + // or the server (client-server mode). + networkObject.AuthorityAppliedParenting = AuthorityApplied || context.SenderId == networkObject.OwnerClientId || context.SenderId == NetworkManager.ServerClientId; + if (!networkObject.AuthorityAppliedParenting && networkManager.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarningServer($"Client-{context.SenderId} sent a ParentSyncMessage but is not the authority of {networkObject.gameObject.name}'s {nameof(NetworkObject)} component!"); + // DANGO-TODO: Still determining if we should not apply this change (I am leaning towards not allowing it). + } +#endif networkObject.SetNetworkParenting(LatestParent, WorldPositionStays); networkObject.ApplyNetworkParenting(RemoveParent); @@ -111,6 +137,32 @@ public void Handle(ref NetworkContext context) networkObject.transform.rotation = Rotation; } networkObject.transform.localScale = Scale; + +#if NGO_DAMODE + // If in distributed authority mode and we are running a DAHost and this is the DAHost, then forward the parent changed message to any remaining clients + if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection && networkManager.DAHost) + { + var size = 0; + var message = this; + + foreach (var client in networkManager.ConnectedClients) + { + if (client.Value.ClientId == networkObject.OwnerClientId || client.Value.ClientId == networkManager.LocalClientId) + { + continue; + } + if (networkObject.IsNetworkVisibleTo(client.Value.ClientId)) + { + size = networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId); + networkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); + } + else + { + Debug.Log($"[DAHost][ParentingProxy] Client-{client.Value.ClientId} has no visibility to {networkObject.name}!"); + } + } + } +#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs index f47237c8cb..9efd3ef73e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -31,11 +31,26 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public unsafe void Handle(ref NetworkContext context) { - var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(WrappedMessage.Metadata.NetworkObjectId, out var networkObject)) { - throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); +#if NGO_DAMODE + // With distributed authority mode, we can send Rpcs before we have been notified the NetworkObject is despawned. + // DANGO-TODO: Should the CMB Service cull out any Rpcs targeting recently despawned NetworkObjects? + // DANGO-TODO: This would require the service to keep track of despawned NetworkObjects since we re-use NetworkObject identifiers. + if (networkManager.DistributedAuthorityMode) + { + if (networkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); + } + return; + } + else +#endif + { + throw new InvalidOperationException($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); + } } var observers = networkObject.Observers; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index e6961fbada..ad822de25c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -14,7 +14,11 @@ public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length); } +#if NGO_DAMODE + public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload, string messageType) +#else public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload) +#endif { ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId); @@ -23,7 +27,11 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId)) { +#if NGO_DAMODE + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context, messageType); +#else networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context); +#endif return false; } @@ -52,7 +60,6 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo reader.Length); } #endif - return true; } @@ -107,7 +114,11 @@ public unsafe void Serialize(FastBufferWriter writer, int targetVersion) public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { +#if NGO_DAMODE + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); +#else return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); +#endif } public void Handle(ref NetworkContext context) @@ -142,7 +153,11 @@ public void Serialize(FastBufferWriter writer, int targetVersion) public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { +#if NGO_DAMODE + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); +#else return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); +#endif } public void Handle(ref NetworkContext context) @@ -179,7 +194,12 @@ public unsafe void Serialize(FastBufferWriter writer, int targetVersion) public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { ByteUnpacker.ReadValuePacked(reader, out SenderClientId); + +#if NGO_DAMODE + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); +#else return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); +#endif } public void Handle(ref NetworkContext context) @@ -197,4 +217,139 @@ public void Handle(ref NetworkContext context) RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams); } } + +#if NGO_DAMODE + internal struct ForwardServerRpcMessage : INetworkMessage + { + public int Version => 0; + public ulong OwnerId; + public NetworkDelivery NetworkDelivery; + public ServerRpcMessage ServerRpcMessage; + + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) + { + writer.WriteValueSafe(OwnerId); + writer.WriteValueSafe(NetworkDelivery); + ServerRpcMessage.Serialize(writer, targetVersion); + } + + public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + reader.ReadValueSafe(out OwnerId); + reader.ReadValueSafe(out NetworkDelivery); + ServerRpcMessage.ReadBuffer = new FastBufferReader(reader, Allocator.Persistent, reader.Length - reader.Position, sizeof(RpcMetadata)); + + // If deserializing failed or this message was deferred. + if (!ServerRpcMessage.Deserialize(reader, ref context, receivedMessageVersion)) + { + // release this reader as the handler will either be invoked later (deferred) or will not be invoked at all. + ServerRpcMessage.ReadBuffer.Dispose(); + return false; + } + return true; + } + + public void Handle(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (networkManager.DAHost) + { + try + { + // Since this is temporary, we will not be collection metrics for this. + // DAHost just forwards the message to the owner + ServerRpcMessage.WriteBuffer = new FastBufferWriter(ServerRpcMessage.ReadBuffer.Length, Allocator.TempJob); + ServerRpcMessage.WriteBuffer.WriteBytesSafe(ServerRpcMessage.ReadBuffer.ToArray()); + networkManager.ConnectionManager.SendMessage(ref ServerRpcMessage, NetworkDelivery, OwnerId); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + else + { + NetworkLog.LogErrorServer($"Received {nameof(ForwardServerRpcMessage)} on client-{networkManager.LocalClientId}! Only DAHost may forward RPC messages!"); + } + ServerRpcMessage.ReadBuffer.Dispose(); + ServerRpcMessage.WriteBuffer.Dispose(); + } + + } + + internal struct ForwardClientRpcMessage : INetworkMessage + { + public int Version => 0; + public bool BroadCast; + public ulong[] TargetClientIds; + public NetworkDelivery NetworkDelivery; + public ClientRpcMessage ClientRpcMessage; + + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) + { + if (TargetClientIds == null) + { + BroadCast = true; + writer.WriteValueSafe(BroadCast); + } + else + { + BroadCast = false; + writer.WriteValueSafe(BroadCast); + writer.WriteValueSafe(TargetClientIds); + } + writer.WriteValueSafe(NetworkDelivery); + ClientRpcMessage.Serialize(writer, targetVersion); + } + + public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + reader.ReadValueSafe(out BroadCast); + + if (!BroadCast) + { + reader.ReadValueSafe(out TargetClientIds); + } + + reader.ReadValueSafe(out NetworkDelivery); + + ClientRpcMessage.ReadBuffer = new FastBufferReader(reader, Allocator.Persistent, reader.Length - reader.Position, sizeof(RpcMetadata)); + // If deserializing failed or this message was deferred. + if (!ClientRpcMessage.Deserialize(reader, ref context, receivedMessageVersion)) + { + // release this reader as the handler will either be invoked later (deferred) or will not be invoked at all. + ClientRpcMessage.ReadBuffer.Dispose(); + return false; + } + return true; + } + + public void Handle(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (networkManager.DAHost) + { + ClientRpcMessage.WriteBuffer = new FastBufferWriter(ClientRpcMessage.ReadBuffer.Length, Allocator.TempJob); + ClientRpcMessage.WriteBuffer.WriteBytesSafe(ClientRpcMessage.ReadBuffer.ToArray()); + // Since this is temporary, we will not be collection metrics for this. + // DAHost just forwards the message to the clients + if (BroadCast) + { + networkManager.ConnectionManager.SendMessage(ref ClientRpcMessage, NetworkDelivery, networkManager.ConnectedClientsIds); + } + else + { + networkManager.ConnectionManager.SendMessage(ref ClientRpcMessage, NetworkDelivery, TargetClientIds); + } + } + else + { + NetworkLog.LogErrorServer($"Received {nameof(ForwardClientRpcMessage)} on client-{networkManager.LocalClientId}! Only DAHost may forward RPC messages!"); + } + ClientRpcMessage.WriteBuffer.Dispose(); + ClientRpcMessage.ReadBuffer.Dispose(); + } + } +#endif + } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs index 44175690ea..25c53a2786 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs @@ -1,3 +1,4 @@ + namespace Unity.Netcode { // Todo: Would be lovely to get this one nicely formatted with all the data it sends in the struct @@ -8,6 +9,7 @@ internal struct SceneEventMessage : INetworkMessage public SceneEventData EventData; + private FastBufferReader m_ReceivedData; public void Serialize(FastBufferWriter writer, int targetVersion) @@ -23,7 +25,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { - ((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData); + var networkManager = (NetworkManager)context.SystemOwner; + networkManager.SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs index b12c0080d7..67d7d255c0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs @@ -4,6 +4,10 @@ internal struct ServerLogMessage : INetworkMessage { public int Version => 0; +#if NGO_DAMODE + public ulong SenderId; +#endif + public NetworkLog.LogType LogType; // It'd be lovely to be able to replace this with FixedString or NativeArray... // But it's not really practical. On the sending side, the user is likely to want @@ -12,20 +16,38 @@ internal struct ServerLogMessage : INetworkMessage // So an allocation is unavoidable here on both sides. public string Message; - public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValueSafe(LogType); BytePacker.WriteValuePacked(writer, Message); +#if NGO_DAMODE + BytePacker.WriteValueBitPacked(writer, SenderId); +#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; + +#if NGO_DAMODE + if ((networkManager.IsServer || networkManager.LocalClient.IsSessionOwner) && networkManager.NetworkConfig.EnableNetworkLogs) +#else if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs) +#endif { reader.ReadValueSafe(out LogType); ByteUnpacker.ReadValuePacked(reader, out Message); +#if NGO_DAMODE + ByteUnpacker.ReadValuePacked(reader, out SenderId); + // If in distributed authority mode and the DAHost is not the session owner, then the DAHost will just forward the message. + if (networkManager.DAHost && networkManager.CurrentSessionOwner != networkManager.LocalClientId) + { + var message = this; + var size = networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, networkManager.CurrentSessionOwner); + networkManager.NetworkMetrics.TrackServerLogSent(networkManager.CurrentSessionOwner, (uint)LogType, size); + return false; + } +#endif return true; } @@ -35,7 +57,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; +#if NGO_DAMODE + var senderId = networkManager.DistributedAuthorityMode ? SenderId : context.SenderId; +#else var senderId = context.SenderId; +#endif networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, context.MessageSize); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs new file mode 100644 index 0000000000..75f7da6bdd --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs @@ -0,0 +1,29 @@ + +#if NGO_DAMODE +namespace Unity.Netcode +{ + internal struct SessionOwnerMessage : INetworkMessage + { + public int Version => 0; + + public ulong SessionOwner; + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + BytePacker.WriteValuePacked(writer, SessionOwner); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + ByteUnpacker.ReadValuePacked(reader, out SessionOwner); + return true; + } + + public unsafe void Handle(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + networkManager.SetSessionOwner(SessionOwner); + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 24699ebf5d..4fd07fa315 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -167,6 +167,30 @@ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetwor { RegisterMessageType(type); } + +#if NGO_DAMODE +#if UNITY_EDITOR + if (EnableMessageOrderConsoleLog) + { + // DANGO-TODO: Remove this when we have some form of message type indices stability in place + // For now, just log the messages and their assigned types for reference purposes. + var networkManager = m_Owner as NetworkManager; + if (networkManager != null) + { + if (networkManager.DistributedAuthorityMode) + { + var messageListing = new StringBuilder(); + messageListing.AppendLine("NGO Message Index to Type Listing:"); + foreach (var message in m_MessageTypes) + { + messageListing.AppendLine($"[{message.Value}][{message.Key.Name}]"); + } + Debug.Log(messageListing); + } + } + } +#endif +#endif } catch (Exception) { @@ -175,6 +199,10 @@ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetwor } } +#if NGO_DAMODE + internal static bool EnableMessageOrderConsoleLog = false; +#endif + public void Dispose() { if (m_Disposed) @@ -823,7 +851,11 @@ internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in N internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in NativeList clientIds) where T : INetworkMessage { +#if UTP_TRANSPORT_2_0_ABOVE + return SendMessage(ref message, delivery, new PointerListWrapper(clientIds.GetUnsafePtr(), clientIds.Length)); +#else return SendMessage(ref message, delivery, new PointerListWrapper((ulong*)clientIds.GetUnsafePtr(), clientIds.Length)); +#endif } internal unsafe void ProcessSendQueues() diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs new file mode 100644 index 0000000000..d4238cd0a3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs @@ -0,0 +1,77 @@ +#if NGO_DAMODE +namespace Unity.Netcode +{ + internal class AuthorityRpcTarget : ServerRpcTarget + { + private ProxyRpcTarget m_AuthorityTarget; + private DirectSendRpcTarget m_DirectSendTarget; + + public override void Dispose() + { + if (m_AuthorityTarget != null) + { + m_AuthorityTarget.Dispose(); + m_AuthorityTarget = null; + } + + if (m_DirectSendTarget != null) + { + m_DirectSendTarget.Dispose(); + m_DirectSendTarget = null; + } + + base.Dispose(); + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + if (behaviour.NetworkManager.DistributedAuthorityMode) + { + // If invoked locally, then send locally + if (behaviour.HasAuthority) + { + if (m_UnderlyingTarget == null) + { + m_UnderlyingTarget = new LocalSendRpcTarget(m_NetworkManager); + } + m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams); + } + else if (behaviour.NetworkManager.DAHost) + { + if (m_DirectSendTarget == null) + { + m_DirectSendTarget = new DirectSendRpcTarget(behaviour.OwnerClientId, m_NetworkManager); + } + else + { + m_DirectSendTarget.ClientId = behaviour.OwnerClientId; + } + m_DirectSendTarget.Send(behaviour, ref message, delivery, rpcParams); + } + else // Otherwise (for now), we always proxy the RPC messages to the owner + { + if (m_AuthorityTarget == null) + { + m_AuthorityTarget = new ProxyRpcTarget(behaviour.OwnerClientId, m_NetworkManager); + } + else + { + // Since the owner can change, for now we will just clear and set the owner each time + m_AuthorityTarget.SetClientId(behaviour.OwnerClientId); + } + m_AuthorityTarget.Send(behaviour, ref message, delivery, rpcParams); + } + } + else + { + // If we are not in distributed authority mode, then we invoke the normal ServerRpc code. + base.Send(behaviour, ref message, delivery, rpcParams); + } + } + + internal AuthorityRpcTarget(NetworkManager manager) : base(manager) + { + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs.meta new file mode 100644 index 0000000000..fe0f8acc6e --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b8f28b7a617fd34faee91e5f88e89ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs index 92e2083163..ffc9b5b07e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs @@ -36,7 +36,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, MessageType = m_NetworkManager.MessageManager.GetMessageType(typeof(RpcMessage)) }; - behaviour.NetworkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0, reader, ref context); + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0, reader, ref context); length = reader.Length; } else @@ -49,8 +49,8 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) { - behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( - behaviour.NetworkManager.LocalClientId, + networkManager.NetworkMetrics.TrackRpcSent( + networkManager.LocalClientId, behaviour.NetworkObject, rpcMethodName, behaviour.__getTypeName(), diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs new file mode 100644 index 0000000000..e7d59152dd --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs @@ -0,0 +1,67 @@ +#if NGO_DAMODE +namespace Unity.Netcode +{ + internal class NotAuthorityRpcTarget : NotServerRpcTarget + { + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + var networkObject = behaviour.NetworkObject; + if (m_NetworkManager.DistributedAuthorityMode) + { + if (m_GroupSendTarget == null) + { + // When mocking the CMB service, we are running a server so create a non-proxy target group + if (m_NetworkManager.DAHost) + { + m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager); + } + else // Otherwise (for now), we always proxy the RPC messages + { + m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager); + } + } + m_GroupSendTarget.Clear(); + + if (behaviour.HasAuthority) + { + foreach (var clientId in networkObject.Observers) + { + if (clientId == behaviour.OwnerClientId) + { + continue; + } + m_GroupSendTarget.Add(clientId); + } + } + else + { + foreach (var clientId in m_NetworkManager.ConnectedClientsIds) + { + if (clientId == behaviour.OwnerClientId || !networkObject.Observers.Contains(clientId)) + { + continue; + } + + if (clientId == m_NetworkManager.LocalClientId) + { + m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + continue; + } + m_GroupSendTarget.Add(clientId); + } + } + + m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams); + } + else + { + base.Send(behaviour, ref message, delivery, rpcParams); + } + } + + internal NotAuthorityRpcTarget(NetworkManager manager) : base(manager) + { + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs.meta new file mode 100644 index 0000000000..6dd6f00aba --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4968ce7e70c277d4a892c1ed209ce4d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs index d348660cd9..30b745e68c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs @@ -2,8 +2,8 @@ namespace Unity.Netcode { internal class NotServerRpcTarget : BaseRpcTarget { - private IGroupRpcTarget m_GroupSendTarget; - private LocalSendRpcTarget m_LocalSendRpcTarget; + protected IGroupRpcTarget m_GroupSendTarget; + protected LocalSendRpcTarget m_LocalSendRpcTarget; public override void Dispose() { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index 8d99c94b92..bc411dc6b4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -61,6 +61,20 @@ public enum SendTo /// If the server is running in dedicated server mode, this is the same as . /// ClientsAndHost, +#if NGO_DAMODE + /// + /// Send this RPC to the authority. + /// In distributed authority mode, this will be the owner of the NetworkObject. + /// In normal client-server mode, this is basically the exact same thing as a server rpc. + /// + Authority, + /// + /// Send this RPC to all non-authority instances. + /// In distributed authority mode, this will be the non-owners of the NetworkObject. + /// In normal client-server mode, this is basically the exact same thing as a client rpc. + /// + NotAuthority, +#endif /// /// This RPC cannot be sent without passing in a target in RpcSendParams. /// @@ -100,7 +114,10 @@ internal RpcTarget(NetworkManager manager) NotMe = new NotMeRpcTarget(manager); Me = new LocalSendRpcTarget(manager); ClientsAndHost = new ClientsAndHostRpcTarget(manager); - +#if NGO_DAMODE + Authority = new AuthorityRpcTarget(manager); + NotAuthority = new NotAuthorityRpcTarget(manager); +#endif m_CachedProxyRpcTargetGroup = new ProxyRpcTargetGroup(manager); m_CachedTargetGroup = new RpcTargetGroup(manager); m_CachedDirectSendTarget = new DirectSendRpcTarget(manager); @@ -122,7 +139,10 @@ public void Dispose() NotMe.Dispose(); Me.Dispose(); ClientsAndHost.Dispose(); - +#if NGO_DAMODE + Authority.Dispose(); + NotAuthority.Dispose(); +#endif m_CachedProxyRpcTargetGroup.Unlock(); m_CachedTargetGroup.Unlock(); m_CachedDirectSendTarget.Unlock(); @@ -134,7 +154,6 @@ public void Dispose() m_CachedProxyRpcTarget.Dispose(); } - /// /// Send to the NetworkObject's current owner. /// Will execute locally if the local process is the owner. @@ -196,6 +215,22 @@ public void Dispose() /// public BaseRpcTarget ClientsAndHost; +#if NGO_DAMODE + /// + /// Send this RPC to the authority. + /// In distributed authority mode, this will be the owner of the NetworkObject. + /// In normal client-server mode, this is basically the exact same thing as a server rpc. + /// + public BaseRpcTarget Authority; + + /// + /// Send this RPC to all non-authority instances. + /// In distributed authority mode, this will be the non-owners of the NetworkObject. + /// In normal client-server mode, this is basically the exact same thing as a client rpc. + /// + public BaseRpcTarget NotAuthority; +#endif + /// /// Send to a specific single client ID. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 09b789ddb9..15e2026432 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -2,7 +2,7 @@ namespace Unity.Netcode { internal class ServerRpcTarget : BaseRpcTarget { - private BaseRpcTarget m_UnderlyingTarget; + protected BaseRpcTarget m_UnderlyingTarget; public override void Dispose() { @@ -15,6 +15,14 @@ public override void Dispose() internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { +#if NGO_DAMODE + if (behaviour.NetworkManager.DistributedAuthorityMode && behaviour.NetworkManager.CMBServiceConnection) + { + UnityEngine.Debug.LogWarning("Sending to Server in Distributed Authority mode is not allowed!"); + return; + } +#endif + if (m_UnderlyingTarget == null) { if (behaviour.NetworkManager.IsServer) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs index 096b2a4c62..912bcf94a0 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs @@ -505,8 +505,13 @@ public static void WriteNativeListDelta(FastBufferWriter writer, ref NativeLi writer.WriteValueSafe(changes); unsafe { +#if UTP_TRANSPORT_2_0_ABOVE + var ptr = value.GetUnsafePtr(); + var prevPtr = previousValue.GetUnsafePtr(); +#else var ptr = (T*)value.GetUnsafePtr(); var prevPtr = (T*)previousValue.GetUnsafePtr(); +#endif for (int i = 0; i < value.Length; ++i) { if (changes.IsSet(i)) @@ -549,7 +554,11 @@ public static void ReadNativeListDelta(FastBufferReader reader, ref NativeLis unsafe { +#if UTP_TRANSPORT_2_0_ABOVE + var ptr = value.GetUnsafePtr(); +#else var ptr = (T*)value.GetUnsafePtr(); +#endif for (var i = 0; i < value.Length; ++i) { if (changes.IsSet(i)) @@ -571,8 +580,8 @@ public static void ReadNativeListDelta(FastBufferReader reader, ref NativeLis public static unsafe void WriteNativeHashSetDelta(FastBufferWriter writer, ref NativeHashSet value, ref NativeHashSet previousValue) where T : unmanaged, IEquatable { // See WriteHashSet; this is the same algorithm, adjusted for the NativeHashSet API - var added = stackalloc T[value.Count()]; - var removed = stackalloc T[previousValue.Count()]; + var added = stackalloc T[value.Count]; + var removed = stackalloc T[previousValue.Count]; var addedCount = 0; var removedCount = 0; foreach (var item in value) @@ -592,8 +601,11 @@ public static unsafe void WriteNativeHashSetDelta(FastBufferWriter writer, re ++removedCount; } } - +#if UTP_TRANSPORT_2_0_ABOVE + if (addedCount + removedCount >= value.Count) +#else if (addedCount + removedCount >= value.Count()) +#endif { writer.WriteByteSafe(1); writer.WriteValueSafe(value); @@ -643,14 +655,21 @@ public static unsafe void WriteNativeHashMapDelta(FastBufferWriter w where TVal : unmanaged { // See WriteDictionary; this is the same algorithm, adjusted for the NativeHashMap API +#if UTP_TRANSPORT_2_0_ABOVE + var added = stackalloc KVPair[value.Count]; + var changed = stackalloc KVPair[value.Count]; + var removed = stackalloc KVPair[previousValue.Count]; +#else var added = stackalloc KeyValue[value.Count()]; var changed = stackalloc KeyValue[value.Count()]; var removed = stackalloc KeyValue[previousValue.Count()]; +#endif var addedCount = 0; var changedCount = 0; var removedCount = 0; foreach (var item in value) { + var hasPrevVal = previousValue.TryGetValue(item.Key, out var prevVal); if (!hasPrevVal) { @@ -673,7 +692,11 @@ public static unsafe void WriteNativeHashMapDelta(FastBufferWriter w } } +#if UTP_TRANSPORT_2_0_ABOVE + if (addedCount + removedCount + changedCount >= value.Count) +#else if (addedCount + removedCount + changedCount >= value.Count()) +#endif { writer.WriteByteSafe(1); writer.WriteValueSafe(value); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index a082b4e929..3921055fd2 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -24,6 +24,7 @@ public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatabl /// The callback to be invoked when the list gets changed /// public event OnListChangedDelegate OnListChanged; + internal override NetworkVariableType Type => NetworkVariableType.NetworkList; /// /// Constructor method for @@ -98,7 +99,7 @@ public override void WriteDelta(FastBufferWriter writer) break; case NetworkListEvent.EventType.Insert: { - writer.WriteValueSafe(element.Index); + BytePacker.WriteValueBitPacked(writer, element.Index); NetworkVariableSerialization.Write(writer, ref element.Value); } break; @@ -109,12 +110,12 @@ public override void WriteDelta(FastBufferWriter writer) break; case NetworkListEvent.EventType.RemoveAt: { - writer.WriteValueSafe(element.Index); + BytePacker.WriteValueBitPacked(writer, element.Index); } break; case NetworkListEvent.EventType.Value: { - writer.WriteValueSafe(element.Index); + BytePacker.WriteValueBitPacked(writer, element.Index); NetworkVariableSerialization.Write(writer, ref element.Value); } break; @@ -130,6 +131,19 @@ public override void WriteDelta(FastBufferWriter writer) /// public override void WriteField(FastBufferWriter writer) { +#if NGO_DAMODE + writer.WriteValueSafe(NetworkVariableSerialization.Type); + if (NetworkVariableSerialization.Type == CollectionItemType.Unmanaged) + { + // Write the size of the unmanaged serialized type as it has a fixed size. This allows the CMB runtime to correctly read the unmanged type. + var placeholder = new T(); + var startPos = writer.Position; + NetworkVariableSerialization.Write(writer, ref placeholder); + var size = writer.Position - startPos; + writer.Seek(startPos); + BytePacker.WriteValueBitPacked(writer, size); + } +#endif writer.WriteValueSafe((ushort)m_List.Length); for (int i = 0; i < m_List.Length; i++) { @@ -141,6 +155,14 @@ public override void WriteField(FastBufferWriter writer) public override void ReadField(FastBufferReader reader) { m_List.Clear(); +#if NGO_DAMODE + // Collection item type is used by the CMB rust service, drop value here. + reader.ReadValueSafe(out CollectionItemType type); + if (type == CollectionItemType.Unmanaged) + { + ByteUnpacker.ReadValueBitPacked(reader, out int _); + } +#endif reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { @@ -189,7 +211,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) break; case NetworkListEvent.EventType.Insert: { - reader.ReadValueSafe(out int index); + ByteUnpacker.ReadValueBitPacked(reader, out int index); var value = new T(); NetworkVariableSerialization.Read(reader, ref value); @@ -261,7 +283,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) break; case NetworkListEvent.EventType.RemoveAt: { - reader.ReadValueSafe(out int index); + ByteUnpacker.ReadValueBitPacked(reader, out int index); T value = m_List[index]; m_List.RemoveAt(index); @@ -289,7 +311,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) break; case NetworkListEvent.EventType.Value: { - reader.ReadValueSafe(out int index); + ByteUnpacker.ReadValueBitPacked(reader, out int index); var value = new T(); NetworkVariableSerialization.Read(reader, ref value); if (index >= m_List.Length) @@ -367,7 +389,7 @@ public IEnumerator GetEnumerator() public void Add(T item) { // check write permissions - if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + if (!CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } @@ -388,7 +410,7 @@ public void Add(T item) public void Clear() { // check write permissions - if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + if (!CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } @@ -414,7 +436,7 @@ public bool Contains(T item) public bool Remove(T item) { // check write permissions - if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + if (!CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } @@ -449,7 +471,7 @@ public int IndexOf(T item) public void Insert(int index, T item) { // check write permissions - if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + if (!CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } @@ -478,7 +500,7 @@ public void Insert(int index, T item) public void RemoveAt(int index) { // check write permissions - if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + if (!CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } @@ -503,7 +525,7 @@ public T this[int index] set { // check write permissions - if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + if (!CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } @@ -551,6 +573,7 @@ public override void Dispose() { m_List.Dispose(); m_DirtyEvents.Dispose(); + base.Dispose(); } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 7b34b618b4..d625c05205 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -21,6 +21,7 @@ public class NetworkVariable : NetworkVariableBase /// The callback to be invoked when the value gets changed /// public OnValueChangedDelegate OnValueChanged; + internal override NetworkVariableType Type => NetworkVariableType.Value; /// /// Constructor for @@ -43,6 +44,19 @@ public NetworkVariable(T value = default, m_PreviousValue = default; } + /// + /// Resets the NetworkVariable when the associated NetworkObject is not spawned + /// + /// the value to reset the NetworkVariable to (if none specified it resets to the default) + public void Reset(T value = default) + { + if (m_NetworkBehaviour == null || m_NetworkBehaviour != null && !m_NetworkBehaviour.NetworkObject.IsSpawned) + { + m_InternalValue = value; + m_PreviousValue = default; + } + } + /// /// The internal value of the NetworkVariable /// @@ -68,10 +82,19 @@ public virtual T Value return; } - if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + // DANGO-TODO: NetworkVariable permissions need to be sorted out/implemented for distributed authority mode +#if NGO_DAMODE + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) + { + throw new InvalidOperationException($"[Client-{m_NetworkManager.LocalClientId}][{m_NetworkBehaviour.name}][{Name}] Write permissions ({WritePerm}) for this client instance is not allowed!"); + } +#else + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) { - throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable"); + throw new InvalidOperationException($"[Client-{m_NetworkManager.LocalClientId}][{m_NetworkBehaviour.name}][{Name}] Write permissions ({WritePerm}) for this client instance is not allowed!"); } +#endif + Set(value); m_IsDisposed = false; @@ -104,6 +127,8 @@ public override void Dispose() } m_PreviousValue = default; + + base.Dispose(); } ~NetworkVariable() diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 927687c692..0fecff8e4a 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -18,6 +18,22 @@ public abstract class NetworkVariableBase : IDisposable /// private protected NetworkBehaviour m_NetworkBehaviour; + private NetworkManager m_InternalNetworkManager; + + internal virtual NetworkVariableType Type => NetworkVariableType.Custom; + + private protected NetworkManager m_NetworkManager + { + get + { + if (m_InternalNetworkManager == null && m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager) + { + m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; + } + return m_InternalNetworkManager; + } + } + public NetworkBehaviour GetBehaviour() { return m_NetworkBehaviour; @@ -29,7 +45,16 @@ public NetworkBehaviour GetBehaviour() /// The NetworkBehaviour the NetworkVariable belongs to public void Initialize(NetworkBehaviour networkBehaviour) { + m_InternalNetworkManager = null; m_NetworkBehaviour = networkBehaviour; + if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager) + { + m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; +#if NGO_DAMODE + // When in distributed authority mode, there is no such thing as server write permissions + InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; +#endif + } } /// @@ -53,7 +78,11 @@ protected NetworkVariableBase( NetworkVariableWritePermission writePerm = DefaultWritePerm) { ReadPerm = readPerm; +#if NGO_DAMODE + InternalWritePerm = writePerm; +#else WritePerm = writePerm; +#endif } /// @@ -73,10 +102,27 @@ protected NetworkVariableBase( /// public readonly NetworkVariableReadPermission ReadPerm; +#if NGO_DAMODE + /// + /// The write permission for this var + /// + public NetworkVariableWritePermission WritePerm + { + get + { + return InternalWritePerm; + } + } + + // DANGO TODO: We have to change the Write Permission in distributed authority. + // (It is too bad we initially declared it as readonly) + internal NetworkVariableWritePermission InternalWritePerm; +#else /// /// The write permission for this var /// public readonly NetworkVariableWritePermission WritePerm; +#endif /// /// Sets whether or not the variable needs to be delta synced @@ -109,7 +155,7 @@ protected void MarkNetworkBehaviourDirty() } return; } - m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject); + m_NetworkBehaviour.NetworkManager.BehaviourUpdater?.AddForUpdate(m_NetworkBehaviour.NetworkObject); } /// @@ -136,6 +182,13 @@ public virtual bool IsDirty() /// Whether or not the client has permission to read public bool CanClientRead(ulong clientId) { +#if NGO_DAMODE + // When in distributed authority mode, everyone can read (but only the owner can write) + if (m_NetworkManager != null && m_NetworkManager.DistributedAuthorityMode) + { + return true; + } +#endif switch (ReadPerm) { default: @@ -201,6 +254,50 @@ internal ulong OwnerClientId() /// public virtual void Dispose() { + m_InternalNetworkManager = null; } } + + /// + /// Enum representing the different types of Network Variables. + /// + public enum NetworkVariableType : byte + { + /// + /// Value + /// Used for all of the basic NetworkVariables that contain a single value + /// + Value = 0, + + /// + /// Custom + /// For any custom implemented extension of the NetworkVariableBase + /// + Custom = 1, + + /// + /// NetworkList + /// + NetworkList = 2 + } + + public enum CollectionItemType : byte + { + /// + /// For any type that is not valid inside a NetworkVariable collection + /// + Unknown = 0, + + /// + /// The following types are valid types inside of NetworkVariable collections + /// + Short = 1, + UShort = 2, + Int = 3, + UInt = 4, + Long = 5, + ULong = 6, + Unmanaged = 7, + } + } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs index c2d2d3e0a2..d2142c09f4 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -1267,6 +1267,15 @@ internal static void InitializeIntegerSerialization() NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; NetworkVariableSerialization.Serializer = new UlongSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; + +#if NGO_DAMODE + NetworkVariableSerialization.Type = CollectionItemType.Short; + NetworkVariableSerialization.Type = CollectionItemType.UShort; + NetworkVariableSerialization.Type = CollectionItemType.Int; + NetworkVariableSerialization.Type = CollectionItemType.UInt; + NetworkVariableSerialization.Type = CollectionItemType.Long; + NetworkVariableSerialization.Type = CollectionItemType.ULong; +#endif } /// @@ -1276,6 +1285,9 @@ internal static void InitializeIntegerSerialization() public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged { NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); +#if NGO_DAMODE + NetworkVariableSerialization.Type = CollectionItemType.Unmanaged; +#endif } /// @@ -1554,6 +1566,14 @@ public static class NetworkVariableSerialization { internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); + +#if NGO_DAMODE + /// + /// The collection item type tells the CMB server how to read the bytes of each item in the collection + /// + internal static CollectionItemType Type = CollectionItemType.Unknown; +#endif + /// /// A callback to check if two values are equal. /// @@ -1725,8 +1745,14 @@ internal static unsafe bool ValueEqualsList(ref NativeList(ref NativeList< return false; } +#if UTP_TRANSPORT_2_0_ABOVE + var aptr = a.GetUnsafePtr(); + var bptr = b.GetUnsafePtr(); +#else var aptr = (TValueType*)a.GetUnsafePtr(); var bptr = (TValueType*)b.GetUnsafePtr(); +#endif for (var i = 0; i < a.Length; ++i) { if (!EqualityEquals(ref aptr[i], ref bptr[i])) @@ -1880,7 +1911,11 @@ internal static bool EqualityEqualsNativeHashSet(ref NativeHashSet a, return true; } +#if UTP_TRANSPORT_2_0_ABOVE + if (a.Count != b.Count) +#else if (a.Count() != b.Count()) +#endif { return false; } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs index 5b3ec1e7ca..b4b4b76e83 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs @@ -95,11 +95,19 @@ public unsafe void NetworkSerialize(BufferSerializer serializer) where T : { if (serializer.IsReader) { +#if UTP_TRANSPORT_2_0_ABOVE + serializer.GetFastBufferReader().ReadBytesSafe(ptr, length); +#else serializer.GetFastBufferReader().ReadBytesSafe((byte*)ptr, length); +#endif } else { +#if UTP_TRANSPORT_2_0_ABOVE + serializer.GetFastBufferWriter().WriteBytesSafe(ptr, length); +#else serializer.GetFastBufferWriter().WriteBytesSafe((byte*)ptr, length); +#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 4879b6c824..6df81e9741 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -18,6 +18,9 @@ internal struct SceneEntry public bool IsAssigned; public Scene Scene; } +#if NGO_DAMODE + public bool IsIntegrationTest() { return false; } +#endif internal Dictionary> SceneNameToSceneHandles = new Dictionary>(); @@ -327,8 +330,12 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode) { var sceneManager = networkManager.SceneManager; - // Don't let client's set this value - if (!networkManager.IsServer) +#if NGO_DAMODE + // Don't let non-authority set this value + if ((!networkManager.DistributedAuthorityMode && !networkManager.IsServer) || (networkManager.DistributedAuthorityMode && !networkManager.LocalClient.IsSessionOwner)) +#else + if (!networkManager.IsServer) +#endif { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs index 240a5c94f6..4df6c4f188 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs @@ -32,5 +32,8 @@ internal interface ISceneManagerHandler void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode); bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager); +#if NGO_DAMODE + bool IsIntegrationTest(); +#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index ba1b0ee64e..2a4caa20f4 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -423,6 +423,22 @@ public bool ActiveSceneSynchronizationEnabled /// internal Dictionary ScenesLoaded = new Dictionary(); +#if NGO_DAMODE + /// + /// Returns the currently loaded scenes that are synchronized with the session owner or server depending upon the selected + /// NetworkManager session mode. + /// + /// + /// The scenes loaded returns all scenes loaded where this returns only the scenes that have been + /// synchronized remotely. This can be useful when using scene validation and excluding certain scenes from being synchronized. + /// + /// List of the known synchronized scenes + public List GetSynchronizedScenes() + { + return ScenesLoaded.Values.ToList(); + } +#endif + /// /// Since Scene.handle is unique per client, we create a look-up table between the client and server to associate server unique scene /// instances with client unique scene instances @@ -550,6 +566,19 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) /// private bool m_DisableValidationWarningMessages; +#if NGO_DAMODE + internal bool HasSceneAuthority() + { + if (!NetworkManager) + { + return false; + } + return (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer) || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner); + } + +#endif + + /// /// Handle NetworkSeneManager clean up /// @@ -614,7 +643,11 @@ internal void EndSceneEvent(uint sceneEventId) internal bool ShouldDeferCreateObject() { // This applies only to remote clients and when scene management is enabled +#if NGO_DAMODE + if (!NetworkManager.NetworkConfig.EnableSceneManagement || HasSceneAuthority()) +#else if (!NetworkManager.NetworkConfig.EnableSceneManagement || NetworkManager.IsServer) +#endif { return false; } @@ -782,10 +815,15 @@ internal NetworkSceneManager(NetworkManager networkManager) // Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene DontDestroyOnLoadScene = networkManager.gameObject.scene; - // Since the server tracks loaded scenes, we need to add any currently loaded scenes on the + // Since the server tracks loaded scenes, we need to add any currently loaded scenes on the // server side when the NetworkManager is started and NetworkSceneManager instantiated when // scene management is enabled. + +#if NGO_DAMODE + if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkManager.NetworkConfig.EnableSceneManagement) +#else if (networkManager.IsServer && networkManager.NetworkConfig.EnableSceneManagement) +#endif { for (int i = 0; i < SceneManager.sceneCount; i++) { @@ -799,11 +837,38 @@ internal NetworkSceneManager(NetworkManager networkManager) UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene); } +#if NGO_DAMODE + internal void InitializeScenesLoaded() + { + if (!NetworkManager.DistributedAuthorityMode) + { + return; + } + + if (HasSceneAuthority() && NetworkManager.NetworkConfig.EnableSceneManagement) + { + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var loadedScene = SceneManager.GetSceneAt(i); + UpdateServerClientSceneHandle(loadedScene.handle, loadedScene.handle, loadedScene); + } + SceneManagerHandler.PopulateLoadedScenes(ref ScenesLoaded, NetworkManager); + } + } +#endif + /// /// Synchronizes clients when the currently active scene is changed /// private void SceneManager_ActiveSceneChanged(Scene current, Scene next) { +#if NGO_DAMODE + if ((!NetworkManager.DistributedAuthorityMode && !NetworkManager.IsServer) || (NetworkManager.DistributedAuthorityMode && !NetworkManager.LocalClient.IsSessionOwner)) + { + return; + } +#endif + // If no clients are connected, then don't worry about notifications if (!(NetworkManager.ConnectedClientsIds.Count > (NetworkManager.IsHost ? 1 : 0))) { @@ -826,7 +891,16 @@ private void SceneManager_ActiveSceneChanged(Scene current, Scene next) var sceneEvent = BeginSceneEvent(); sceneEvent.SceneEventType = SceneEventType.ActiveSceneChanged; sceneEvent.ActiveSceneHash = BuildIndexToHash[next.buildIndex]; +#if NGO_DAMODE + var sessionOwner = NetworkManager.ServerClientId; + if (NetworkManager.DistributedAuthorityMode) + { + sessionOwner = NetworkManager.CurrentSessionOwner; + } + SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != sessionOwner).ToArray()); +#else SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray()); +#endif EndSceneEvent(sceneEvent.SceneEventId); } } @@ -859,12 +933,20 @@ internal bool ValidateSceneBeforeLoading(int sceneIndex, string sceneName, LoadS } if (!validated && !m_DisableValidationWarningMessages) { + +#if NGO_DAMODE + var serverHostorClient = "Client"; + if (HasSceneAuthority()) + { + serverHostorClient = NetworkManager.DistributedAuthorityMode ? "Session Owner" : NetworkManager.IsHost ? "Host" : "Server"; + } +#else var serverHostorClient = "Client"; if (NetworkManager.IsServer) { serverHostorClient = NetworkManager.IsHost ? "Host" : "Server"; } - +#endif Debug.LogWarning($"Scene {sceneName} of Scenes in Build Index {sceneIndex} being loaded in {loadSceneMode} mode failed validation on the {serverHostorClient}!"); } return validated; @@ -961,7 +1043,7 @@ internal void SetTheSceneBeingSynchronized(int serverSceneHandle) // This could be the scenario where NetworkManager.DontDestroy is false and we are creating the first NetworkObject (client side) to be in the DontDestroyOnLoad scene // Otherwise, this is some other specific scenario that we might not be handling currently. - Debug.LogWarning($"[{nameof(SceneEventData)}- Scene Handle Mismatch] {nameof(serverSceneHandle)} could not be found in {nameof(ServerSceneHandleToClientSceneHandle)}. Using the currently active scene."); + Debug.LogWarning($"[{nameof(SceneEventData)}- Scene Handle Mismatch] {nameof(serverSceneHandle)} ({serverSceneHandle}) could not be found in {nameof(ServerSceneHandleToClientSceneHandle)}. Using the currently active scene."); } } } @@ -976,7 +1058,11 @@ internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdH if (ScenePlacedObjects.ContainsKey(globalObjectIdHash)) { var sceneHandle = SceneBeingSynchronized.handle; +#if NGO_DAMODE + if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0 && ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle.Value)) +#else if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0) +#endif { sceneHandle = ServerSceneHandleToClientSceneHandle[networkSceneHandle.Value]; } @@ -1000,13 +1086,34 @@ private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds) // Silently return as there is nothing to be done return; } - var message = new SceneEventMessage + var sceneEvent = SceneEventDataStore[sceneEventId]; +#if NGO_DAMODE + sceneEvent.SenderClientId = NetworkManager.LocalClientId; + + if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) { - EventData = SceneEventDataStore[sceneEventId] - }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, targetClientIds); + foreach (var clientId in targetClientIds) + { + sceneEvent.TargetClientId = clientId; + var message = new SceneEventMessage + { + EventData = sceneEvent, + }; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId); + NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEvent.SceneEventType, SceneNameFromHash(sceneEvent.SceneHash), size); + } + } + else +#endif + { + var message = new SceneEventMessage + { + EventData = sceneEvent, + }; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, targetClientIds); + NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size); + } - NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size); } /// @@ -1021,12 +1128,28 @@ private SceneEventProgress ValidateSceneEventUnloading(Scene scene) Debug.LogWarning($"{nameof(LoadScene)} was called, but {nameof(NetworkConfig.EnableSceneManagement)} was not enabled! Enable {nameof(NetworkConfig.EnableSceneManagement)} prior to starting a client, host, or server prior to using {nameof(NetworkSceneManager)}!"); return new SceneEventProgress(null, SceneEventProgressStatus.SceneManagementNotEnabled); } +#if NGO_DAMODE + if (!HasSceneAuthority()) + { + if (NetworkManager.DistributedAuthorityMode) + { + Debug.LogWarning($"[{nameof(SceneEventProgressStatus.SessionOwnerOnlyAction)}][Unload] Clients cannot invoke the {nameof(UnloadScene)} method!"); + return new SceneEventProgress(null, SceneEventProgressStatus.SessionOwnerOnlyAction); + } + else + { + Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Unload] Clients cannot invoke the {nameof(UnloadScene)} method!"); + return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction); + } + } +#else if (!NetworkManager.IsServer) { Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Unload] Clients cannot invoke the {nameof(UnloadScene)} method!"); return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction); } +#endif if (!scene.isLoaded) { @@ -1049,12 +1172,29 @@ private SceneEventProgress ValidateSceneEventLoading(string sceneName) Debug.LogWarning($"{nameof(LoadScene)} was called, but {nameof(NetworkConfig.EnableSceneManagement)} was not enabled! Enable {nameof(NetworkConfig.EnableSceneManagement)} prior to starting a client, host, or server prior to using {nameof(NetworkSceneManager)}!"); return new SceneEventProgress(null, SceneEventProgressStatus.SceneManagementNotEnabled); } - +#if NGO_DAMODE + if (!HasSceneAuthority()) + { + if (NetworkManager.DistributedAuthorityMode) + { + Debug.LogWarning($"[{nameof(SceneEventProgressStatus.SessionOwnerOnlyAction)}][Load] Only the session owner can invoke the {nameof(LoadScene)} method!"); + return new SceneEventProgress(null, SceneEventProgressStatus.SessionOwnerOnlyAction); + } + else + { + Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Load] Clients cannot invoke the {nameof(LoadScene)} method!"); + return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction); + } + } +#else if (!NetworkManager.IsServer) { + Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Load] Clients cannot invoke the {nameof(LoadScene)} method!"); return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction); } +#endif + return ValidateSceneEvent(sceneName); } @@ -1111,18 +1251,40 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode; sceneEventData.ClientsTimedOut = clientsThatTimedOut; - var message = new SceneEventMessage +#if NGO_DAMODE + + if (NetworkManager.DistributedAuthorityMode) { - EventData = sceneEventData - }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ConnectedClientsIds); + SendSceneEventData(sceneEventData.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); + } + else +#endif + { + var message = new SceneEventMessage + { + EventData = sceneEventData + }; - NetworkManager.NetworkMetrics.TrackSceneEventSent( - NetworkManager.ConnectedClientsIds, - (uint)sceneEventProgress.SceneEventType, - SceneNameFromHash(sceneEventProgress.SceneHash), - size); + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ConnectedClientsIds); + NetworkManager.NetworkMetrics.TrackSceneEventSent( + NetworkManager.ConnectedClientsIds, + (uint)sceneEventProgress.SceneEventType, + SceneNameFromHash(sceneEventProgress.SceneHash), + size); + } +#if NGO_DAMODE + // Send a local notification to the session owner that all clients are done loading or unloading + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventProgress.SceneEventType, + SceneName = SceneNameFromHash(sceneEventProgress.SceneHash), + ClientId = NetworkManager.CurrentSessionOwner, + LoadSceneMode = sceneEventProgress.LoadSceneMode, + ClientsThatCompleted = clientsThatCompleted, + ClientsThatTimedOut = clientsThatTimedOut, + }); +#else // Send a local notification to the server that all clients are done loading or unloading OnSceneEvent?.Invoke(new SceneEvent() { @@ -1133,6 +1295,7 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress ClientsThatCompleted = clientsThatCompleted, ClientsThatTimedOut = clientsThatTimedOut, }); +#endif if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted) { @@ -1159,6 +1322,7 @@ public SceneEventProgressStatus UnloadScene(Scene scene) var sceneName = scene.name; var sceneHandle = scene.handle; + if (!scene.isLoaded) { Debug.LogWarning($"{nameof(UnloadScene)} was called, but the scene {scene.name} is not currently loaded!"); @@ -1177,6 +1341,16 @@ public SceneEventProgressStatus UnloadScene(Scene scene) return SceneEventProgressStatus.InternalNetcodeError; } +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + if (ClientSceneHandleToServerSceneHandle.ContainsKey(sceneHandle)) + { + sceneHandle = ClientSceneHandleToServerSceneHandle[sceneHandle]; + } + } +#endif + // 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. @@ -1193,9 +1367,29 @@ 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; - ScenesLoaded.Remove(scene.handle); sceneEventProgress.SceneEventId = sceneEventData.SceneEventId; sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded; + +#if NGO_DAMODE + if (!RemoveServerClientSceneHandle(sceneEventData.SceneHandle, scene.handle)) + { + Debug.LogError($"Failed to remove {SceneNameFromHash(sceneEventData.SceneHash)} scene handles [Server ({sceneEventData.SceneHandle})][Local({scene.handle})]"); + } + + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); + + // Notify local server that a scene is going to be unloaded + OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = sceneUnload, + SceneEventType = sceneEventData.SceneEventType, + LoadSceneMode = sceneEventData.LoadSceneMode, + SceneName = sceneName, + ClientId = NetworkManager.LocalClientId // Session owner can only invoke this + }); + + OnUnload?.Invoke(NetworkManager.LocalClientId, sceneName, sceneUnload); +#else var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); // Notify local server that a scene is going to be unloaded OnSceneEvent?.Invoke(new SceneEvent() @@ -1208,7 +1402,7 @@ public SceneEventProgressStatus UnloadScene(Scene scene) }); OnUnload?.Invoke(NetworkManager.ServerClientId, sceneName, sceneUnload); - +#endif //Return the status return sceneEventProgress.Status; } @@ -1245,13 +1439,20 @@ private void OnClientUnloadScene(uint sceneEventId) // currently active scene. var networkManager = NetworkManager; SceneManagerHandler.MoveObjectsFromSceneToDontDestroyOnLoad(ref networkManager, scene); - m_IsSceneEventActive = true; var sceneEventProgress = new SceneEventProgress(NetworkManager) { SceneEventId = sceneEventData.SceneEventId, - OnSceneEventCompleted = OnSceneUnloaded + OnSceneEventCompleted = OnSceneUnloaded, }; + +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SceneEventProgressTracking.Add(sceneEventData.SceneEventProgressId, sceneEventProgress); + } +#endif + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); SceneManagerHandler.StopTrackingScene(sceneHandle, sceneName, NetworkManager); @@ -1285,27 +1486,48 @@ private void OnSceneUnloaded(uint sceneEventId) // If we are shutdown or about to shutdown, then ignore this event if (!NetworkManager.IsListening || NetworkManager.ShutdownInProgress) { +#if NGO_DAMODE + EndSceneEvent(sceneEventId); +#endif return; } // Migrate the NetworkObjects marked to not be destroyed with the scene into the currently active scene MoveObjectsFromDontDestroyOnLoadToScene(SceneManager.GetActiveScene()); - var sceneEventData = SceneEventDataStore[sceneEventId]; + +#if NGO_DAMODE + if (HasSceneAuthority()) + { + var sessionOwner = NetworkManager.DistributedAuthorityMode ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; +#else // First thing we do, if we are a server, is to send the unload scene event. if (NetworkManager.IsServer) { + var sessionOwner = NetworkManager.ServerClientId; +#endif + // Server sends the unload scene notification after unloading because it will despawn all scene relative in-scene NetworkObjects // If we send this event to all clients before the server is finished unloading they will get warning about an object being // despawned that no longer exists - SendSceneEventData(sceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray()); - - //Only if we are a host do we want register having loaded for the associated SceneEventProgress - if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsHost) + SendSceneEventData(sceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != sessionOwner).ToArray()); +#if NGO_DAMODE + //Only if we are session owner do we want register having loaded for the associated SceneEventProgress + if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && HasSceneAuthority()) +#else + if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsServer) +#endif { - SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId); + SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(sessionOwner); } } +#if NGO_DAMODE + else if (NetworkManager.DistributedAuthorityMode) + { + SceneEventProgressTracking.Remove(sceneEventData.SceneEventProgressId); + m_IsSceneEventActive = false; + } +#endif // Next we prepare to send local notifications for unload complete sceneEventData.SceneEventType = SceneEventType.UnloadComplete; @@ -1316,17 +1538,37 @@ private void OnSceneUnloaded(uint sceneEventId) SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, SceneName = SceneNameFromHash(sceneEventData.SceneHash), +#if NGO_DAMODE + ClientId = NetworkManager.LocalClientId, +#else ClientId = NetworkManager.IsServer ? NetworkManager.ServerClientId : NetworkManager.LocalClientId +#endif }); OnUnloadComplete?.Invoke(NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash)); +#if NGO_DAMODE + if (!HasSceneAuthority()) + { + sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + var message = new SceneEventMessage + { + EventData = sceneEventData, + }; + // This might seem like it needs more logic to determine the target, but the only scenario where we send to the session owner is if the + // current instance is the DAHost. + var target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); + NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); + } +#else // Clients send a notification back to the server they have completed the unload scene event if (!NetworkManager.IsServer) { SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); } - +#endif EndSceneEvent(sceneEventId); // This scene event is now considered "complete" m_IsSceneEventActive = false; @@ -1357,7 +1599,16 @@ internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) SceneEventId = sceneEventId, OnSceneEventCompleted = EmptySceneUnloadedOperation }; +#if NGO_DAMODE + if (ClientSceneHandleToServerSceneHandle.ContainsKey(keyHandleEntry.Value.handle)) + { + var serverSceneHandle = ClientSceneHandleToServerSceneHandle[keyHandleEntry.Value.handle]; + ServerSceneHandleToClientSceneHandle.Remove(serverSceneHandle); + } + ClientSceneHandleToServerSceneHandle.Remove(keyHandleEntry.Value.handle); +#endif var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress); + SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload); } } @@ -1428,6 +1679,19 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc sceneEventProgress.SceneEventId = sceneEventId; sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded; var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); + +#if NGO_DAMODE + // Notify the local server that a scene loading event has begun + OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = sceneLoad, + SceneEventType = sceneEventData.SceneEventType, + LoadSceneMode = sceneEventData.LoadSceneMode, + SceneName = sceneName, + ClientId = NetworkManager.LocalClientId + }); + OnLoad?.Invoke(NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); +#else // Notify the local server that a scene loading event has begun OnSceneEvent?.Invoke(new SceneEvent() { @@ -1437,8 +1701,8 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc SceneName = sceneName, ClientId = NetworkManager.ServerClientId }); - - OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); + OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); +#endif //Return our scene progress instance return sceneEventProgress.Status; @@ -1459,7 +1723,11 @@ internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scen { s_Instances.Add(networkManager, new List()); } +#if NGO_DAMODE + var clientId = networkManager.LocalClientId; +#else var clientId = networkManager.IsServer ? NetworkManager.ServerClientId : networkManager.LocalClientId; +#endif s_Instances[networkManager].Add(new SceneUnloadEventHandler(networkSceneManager, scene, clientId, loadSceneMode, asyncOperation)); } @@ -1594,8 +1862,17 @@ private void OnClientSceneLoadingEvent(uint sceneEventId) var sceneEventProgress = new SceneEventProgress(NetworkManager) { SceneEventId = sceneEventId, - OnSceneEventCompleted = OnSceneLoaded + OnSceneEventCompleted = OnSceneLoaded, + Status = SceneEventProgressStatus.Started, }; + +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SceneEventProgressTracking.Add(sceneEventData.SceneEventProgressId, sceneEventProgress); + m_IsSceneEventActive = true; + } +#endif var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress); OnSceneEvent?.Invoke(new SceneEvent() @@ -1619,6 +1896,9 @@ private void OnSceneLoaded(uint sceneEventId) // If we are shutdown or about to shutdown, then ignore this event if (!NetworkManager.IsListening || NetworkManager.ShutdownInProgress) { +#if NGO_DAMODE + EndSceneEvent(sceneEventId); +#endif return; } @@ -1634,6 +1914,33 @@ private void OnSceneLoaded(uint sceneEventId) SceneManager.SetActiveScene(nextScene); } +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + var networkSceneHandle = nextScene.handle; + if (!HasSceneAuthority()) + { + networkSceneHandle = sceneEventData.SceneHandle; + } + + // Update the server scene handle to client scene handle look up table + if (!UpdateServerClientSceneHandle(networkSceneHandle, nextScene.handle, nextScene)) + { + // If the exact same handle exists then there are problems with using handles + Debug.LogWarning($"Server Scene Handle ({networkSceneHandle}) already exist! Happened during scene load of {nextScene.name} with the local handle ({nextScene.handle})"); + } + } + else if (NetworkManager.IsServer) + { + // Update the server scene handle to client scene handle look up table + if (!UpdateServerClientSceneHandle(nextScene.handle, nextScene.handle, nextScene)) + { + // If the exact same handle exists then there are problems with using handles + Debug.LogWarning($"Server Scene Handle ({nextScene.handle}) already exist! Happened during scene load of {nextScene.name} with the local handle ({nextScene.handle})"); + } + } +#endif + //Get all NetworkObjects loaded by the scene PopulateScenePlacedObjects(nextScene); @@ -1649,29 +1956,42 @@ private void OnSceneLoaded(uint sceneEventId) // When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do // not destroy temporary scene are moved into the active scene IsSpawnedObjectsPendingInDontDestroyOnLoad = false; - +#if NGO_DAMODE + if (HasSceneAuthority()) + { + OnSessionOwnerLoadedScene(sceneEventId, nextScene); +#else if (NetworkManager.IsServer) { OnServerLoadedScene(sceneEventId, nextScene); +#endif } else { - // For the client, we make a server scene handle to client scene handle look up table - if (!UpdateServerClientSceneHandle(sceneEventData.SceneHandle, nextScene.handle, nextScene)) +#if NGO_DAMODE + if (!NetworkManager.DistributedAuthorityMode) +#endif { - // If the exact same handle exists then there are problems with using handles - throw new Exception($"Server Scene Handle ({sceneEventData.SceneHandle}) already exist! Happened during scene load of {nextScene.name} with Client Handle ({nextScene.handle})"); + // For the client, we make a server scene handle to client scene handle look up table + if (!UpdateServerClientSceneHandle(sceneEventData.SceneHandle, nextScene.handle, nextScene)) + { + // If the exact same handle exists then there are problems with using handles + throw new Exception($"Server Scene Handle ({sceneEventData.SceneHandle}) already exist! Happened during scene load of {nextScene.name} with Client Handle ({nextScene.handle})"); + } } - OnClientLoadedScene(sceneEventId, nextScene); } } /// - /// Server side: + /// Server/SessionOwner side: /// On scene loaded callback method invoked by OnSceneLoading only /// +#if NGO_DAMODE + private void OnSessionOwnerLoadedScene(uint sceneEventId, Scene scene) +#else private void OnServerLoadedScene(uint sceneEventId, Scene scene) +#endif { var sceneEventData = SceneEventDataStore[sceneEventId]; // Register in-scene placed NetworkObjects with spawn manager @@ -1681,9 +2001,15 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) { if (!keyValuePairBySceneHandle.Value.IsPlayerObject) { +#if NGO_DAMODE + // All in-scene placed NetworkObjects default to being owned by the server + NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, + NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, NetworkManager.LocalClientId, true); +#else // All in-scene placed NetworkObjects default to being owned by the server NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, NetworkManager.ServerClientId, true); +#endif } } } @@ -1694,6 +2020,35 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) // Set the server's scene's handle so the client can build a look up table sceneEventData.SceneHandle = scene.handle; +#if NGO_DAMODE + var sessionOwner = NetworkManager.ServerClientId; + // Send all clients the scene load event + if (NetworkManager.DistributedAuthorityMode) + { + sessionOwner = NetworkManager.CurrentSessionOwner; + } + + SendSceneEventData(sceneEventData.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != sessionOwner).ToArray()); + + m_IsSceneEventActive = false; + //First, notify local server that the scene was loaded + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = SceneEventType.LoadComplete, + LoadSceneMode = sceneEventData.LoadSceneMode, + SceneName = SceneNameFromHash(sceneEventData.SceneHash), + ClientId = NetworkManager.LocalClientId, + Scene = scene, + }); + + OnLoadComplete?.Invoke(NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); + + //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress + if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsHost) + { + SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.LocalClientId); + } +#else // Send all clients the scene load event for (int j = 0; j < NetworkManager.ConnectedClientsList.Count; j++) { @@ -1709,7 +2064,6 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEventData.SceneEventType, scene.name, size); } } - m_IsSceneEventActive = false; //First, notify local server that the scene was loaded OnSceneEvent?.Invoke(new SceneEvent() @@ -1728,6 +2082,7 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) { SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId); } +#endif EndSceneEvent(sceneEventId); } @@ -1741,16 +2096,43 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) sceneEventData.DeserializeScenePlacedObjects(); sceneEventData.SceneEventType = SceneEventType.LoadComplete; - SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); - m_IsSceneEventActive = false; - - // Process any pending create object messages that the client received while loading a scene - ProcessDeferredCreateObjectMessages(); - // Notify local client that the scene was loaded - OnSceneEvent?.Invoke(new SceneEvent() +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) { - SceneEventType = SceneEventType.LoadComplete, + sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + var message = new SceneEventMessage + { + EventData = sceneEventData, + }; + var target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); + NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); + } + else +#endif + { + SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); + } + + m_IsSceneEventActive = false; + + // Process any pending create object messages that the client received while loading a scene + ProcessDeferredCreateObjectMessages(); + +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SceneEventProgressTracking.Remove(sceneEventData.SceneEventProgressId); + m_IsSceneEventActive = false; + } +#endif + + // Notify local client that the scene was loaded + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = SceneEventType.LoadComplete, LoadSceneMode = sceneEventData.LoadSceneMode, SceneName = SceneNameFromHash(sceneEventData.SceneHash), ClientId = NetworkManager.LocalClientId, @@ -1820,25 +2202,58 @@ internal void SynchronizeNetworkObjects(ulong clientId) continue; } sceneEventData.SceneHash = SceneHashFromNameOrPath(scene.path); - sceneEventData.SceneHandle = scene.handle; + +#if NGO_DAMODE + // If we are just a normal client, then always use the server scene handle + if (NetworkManager.DistributedAuthorityMode) + { + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[scene.handle]; + } + else +#endif + { + sceneEventData.SceneHandle = scene.handle; + } } else if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) { continue; } - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); +#if NGO_DAMODE + // If we are just a normal client and in distributed authority mode, then always use the known server scene handle + if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); + } + else +#endif + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + } } sceneEventData.AddSpawnedNetworkObjects(); sceneEventData.AddDespawnedInSceneNetworkObjects(); - var message = new SceneEventMessage { EventData = sceneEventData }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, clientId); + + var size = 0; +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) + { + size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId); + } + else +#endif + { + size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, clientId); + } NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEventData.SceneEventType, "", size); + // Notify the local server that the client has been sent the synchronize event OnSceneEvent?.Invoke(new SceneEvent() { @@ -1975,12 +2390,20 @@ private void ClientLoadedSynchronization(uint sceneEventId) responseSceneEventData.SceneEventType = SceneEventType.LoadComplete; responseSceneEventData.SceneHash = sceneEventData.ClientSceneHash; - + var target = NetworkManager.ServerClientId; +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + responseSceneEventData.SenderClientId = NetworkManager.LocalClientId; + responseSceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; + target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; + } +#endif var message = new SceneEventMessage { EventData = responseSceneEventData }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId); + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); NetworkManager.NetworkMetrics.TrackSceneEventSent(NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size); @@ -2064,11 +2487,15 @@ private void HandleClientSceneEvent(uint sceneEventId) SceneManager.SetActiveScene(scene); } } + EndSceneEvent(sceneEventId); break; } case SceneEventType.ObjectSceneChanged: { +#if !NGO_DAMODE MigrateNetworkObjectsIntoScenes(); +#endif + EndSceneEvent(sceneEventId); break; } case SceneEventType.Load: @@ -2112,11 +2539,38 @@ private void HandleClientSceneEvent(uint sceneEventId) ProcessDeferredCreateObjectMessages(); sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete; - SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + var message = new SceneEventMessage + { + EventData = sceneEventData, + }; + var target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); + NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); + } + else +#endif + { + SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); + } // All scenes are synchronized, let the server know we are done synchronizing NetworkManager.IsConnectedClient = true; +#if NGO_DAMODE + // With distributed authority, either the client-side automatically spawns the default assigned player prefab or + // if AutoSpawnPlayerPrefabClientSide is disabled the client-side will determine what player prefab to spawn and + // when it gets spawned. + if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide) + { + NetworkManager.ConnectionManager.CreateAndSpawnPlayer(NetworkManager.LocalClientId); + } +#endif + // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); @@ -2142,6 +2596,10 @@ private void HandleClientSceneEvent(uint sceneEventId) OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId); + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"[Client-{NetworkManager.LocalClientId}][Scene Management Enabled] Synchronization complete!"); + } EndSceneEvent(sceneEventId); } break; @@ -2193,11 +2651,20 @@ private void HandleClientSceneEvent(uint sceneEventId) } } + +#if NGO_DAMODE + /// + /// Session Owner Side: + /// Handles incoming Scene_Event messages for the current session owner + /// + private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) +#else /// /// Server Side: /// Handles incoming Scene_Event messages for host or server /// private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) +#endif { var sceneEventData = SceneEventDataStore[sceneEventId]; switch (sceneEventData.SceneEventType) @@ -2244,6 +2711,35 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) } case SceneEventType.SynchronizeComplete: { + + + // At this point the client is considered fully "connected" +#if NGO_DAMODE + if ((NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) || !NetworkManager.DistributedAuthorityMode) + { + if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) + { + NetworkManager.ConnectionManager.AddClient(clientId); + } + + // Notify the local server that a client has finished synchronizing + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventData.SceneEventType, + SceneName = string.Empty, + ClientId = clientId + }); + if (NetworkManager.ConnectedClients.ContainsKey(clientId)) + { + NetworkManager.ConnectedClients[clientId].IsConnected = true; + } + } + else + { + EndSceneEvent(sceneEventId); + return; + } +#else // Notify the local server that a client has finished synchronizing OnSceneEvent?.Invoke(new SceneEvent() { @@ -2251,9 +2747,8 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) SceneName = string.Empty, ClientId = clientId }); - - // At this point the client is considered fully "connected" NetworkManager.ConnectedClients[clientId].IsConnected = true; +#endif // All scenes are synchronized, let the server know we are done synchronizing OnSynchronizeComplete?.Invoke(clientId); @@ -2285,6 +2780,9 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) ClientId = clientId }); } +#if NGO_DAMODE + NetworkManager.SpawnManager.DistributeNetworkObjects(clientId); +#endif EndSceneEvent(sceneEventId); break; } @@ -2296,6 +2794,14 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) } } + // TODO: See if this short circuit is still needed post scene management implementation. It's possible that these tests will become superfluous. +#if NGO_DAMODE + /// + /// Skips scene handling to be able to test CMB DA_NGO Codec tests + /// + internal bool SkipSceneHandling; +#endif + /// /// Both Client and Server: Incoming scene event entry point /// @@ -2306,9 +2812,54 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) if (NetworkManager != null) { var sceneEventData = BeginSceneEvent(); - sceneEventData.Deserialize(reader); - +#if NGO_DAMODE + if (SkipSceneHandling) + { + return; + } + // DA HOST Will keep track of session owner and if it is not the scene owner it will forward the message + // to the current session owner + if (NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) + { + // If the event is server directed + if (!sceneEventData.IsSceneEventClientSide()) + { + // If the DAHost is not the session owner, then forward the message to the current session owner + if (NetworkManager.CurrentSessionOwner != NetworkManager.LocalClientId) + { + var message = new SceneEventMessage() + { + EventData = sceneEventData, + }; + // Forward synchronization to client then exit early because DAHost is not the current session owner + NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.CurrentSessionOwner); + EndSceneEvent(sceneEventData.SceneEventId); + return; + } + } + else + { + // DAHost will forward any messages not targeting the DAHost to the targeted client + if (sceneEventData.TargetClientId != NetworkManager.LocalClientId) + { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogInfoServer($"[Forward To: Client-{sceneEventData.TargetClientId}][{Enum.GetName(typeof(SceneEventType), sceneEventData.SceneEventType)}]"); + } + sceneEventData.ForwardSynchronization = sceneEventData.SceneEventType == SceneEventType.Synchronize; + sceneEventData.IsForwarding = true; + var message = new SceneEventMessage() + { + EventData = sceneEventData, + }; + NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, sceneEventData.TargetClientId); + EndSceneEvent(sceneEventData.SceneEventId); + return; + } + } + } +#endif NetworkManager.NetworkMetrics.TrackSceneEventReceived( clientId, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), reader.Length); @@ -2335,7 +2886,16 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) } else { +#if NGO_DAMODE + var sendingClient = clientId; + if (NetworkManager.DistributedAuthorityMode) + { + sendingClient = sceneEventData.SenderClientId; + } + HandleSessionOwnerEvent(sceneEventData.SceneEventId, sendingClient); +#else HandleServerSceneEvent(sceneEventData.SceneEventId, clientId); +#endif } } else @@ -2367,9 +2927,19 @@ internal void MoveObjectsToDontDestroyOnLoad() if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); +#if NGO_DAMODE + // When temporarily migrating to the DDOL, adjust the network and origin scene handles so no messages are generated + // about objects being moved to a new scene. + networkObject.NetworkSceneHandle = ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; + networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; +#endif } } +#if NGO_DAMODE + else if (networkObject.HasAuthority) +#else else if (NetworkManager.IsServer) +#endif { networkObject.Despawn(); } @@ -2449,6 +3019,23 @@ internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) // back into the currently active scene if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) { +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + // When migrating out of the DDOL to the currently active scene, adjust the network and origin scene handles so no messages are generated + // about objects being moved to a new scene. + if (SceneManagerHandler.IsIntegrationTest() && SceneManager.GetActiveScene() == scene) + { + networkObject.NetworkSceneHandle = scene.handle; + } + else + { + networkObject.NetworkSceneHandle = ClientSceneHandleToServerSceneHandle[scene.handle]; + } + networkObject.SceneOriginHandle = scene.handle; + } +#endif + SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); } } @@ -2459,7 +3046,28 @@ internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) /// Holds a list of scene handles (server-side relative) and NetworkObjects migrated into it /// during the current frame. /// +#if NGO_DAMODE + internal Dictionary>> ObjectsMigratedIntoNewScene = new Dictionary>>(); +#else internal Dictionary> ObjectsMigratedIntoNewScene = new Dictionary>(); +#endif + + internal bool IsSceneEventInProgress() + { + if (!NetworkManager.NetworkConfig.EnableSceneManagement) + { + return false; + } + + foreach (var sceneEventEntry in SceneEventProgressTracking) + { + if (!sceneEventEntry.Value.HasTimedOut() && sceneEventEntry.Value.SceneEventType != SceneEventType.Synchronize && sceneEventEntry.Value.Status == SceneEventProgressStatus.Started) + { + return true; + } + } + return false; + } /// /// Handles notifying clients when a NetworkObject has been migrated into a new scene @@ -2467,7 +3075,11 @@ internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) { // Really, this should never happen but in case it does +#if NGO_DAMODE + if (!networkObject.HasAuthority) +#else if (!NetworkManager.IsServer) +#endif { if (NetworkManager.LogLevel == LogLevel.Developer) { @@ -2496,20 +3108,31 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) // Don't notify if a scene event is in progress // Note: This does not apply to SceneEventType.Synchronize since synchronization isn't a global connected client event. - foreach (var sceneEventEntry in SceneEventProgressTracking) + if (IsSceneEventInProgress()) { - if (!sceneEventEntry.Value.HasTimedOut() && sceneEventEntry.Value.Status == SceneEventProgressStatus.Started) - { - return; - } + return; + } +#if NGO_DAMODE + // Otherwise, add the NetworkObject into the list of NetworkObjects who's scene has changed + if (!ObjectsMigratedIntoNewScene.ContainsKey(networkObject.NetworkSceneHandle)) + { + ObjectsMigratedIntoNewScene.Add(networkObject.NetworkSceneHandle, new Dictionary>()); } + if (!ObjectsMigratedIntoNewScene[networkObject.NetworkSceneHandle].ContainsKey(NetworkManager.LocalClientId)) + { + ObjectsMigratedIntoNewScene[networkObject.NetworkSceneHandle].Add(NetworkManager.LocalClientId, new List()); + } + + ObjectsMigratedIntoNewScene[networkObject.NetworkSceneHandle][NetworkManager.LocalClientId].Add(networkObject); +#else // Otherwise, add the NetworkObject into the list of NetworkObjects who's scene has changed if (!ObjectsMigratedIntoNewScene.ContainsKey(networkObject.gameObject.scene.handle)) { ObjectsMigratedIntoNewScene.Add(networkObject.gameObject.scene.handle, new List()); } ObjectsMigratedIntoNewScene[networkObject.gameObject.scene.handle].Add(networkObject); +#endif } /// @@ -2517,6 +3140,46 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) /// or invoked by when a client finishes /// synchronization. /// + +#if NGO_DAMODE + internal void MigrateNetworkObjectsIntoScenes() + { + try + { + foreach (var sceneEntry in ObjectsMigratedIntoNewScene) + { + if (ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEntry.Key)) + { + var clientSceneHandle = ServerSceneHandleToClientSceneHandle[sceneEntry.Key]; + foreach (var ownerEntry in sceneEntry.Value) + { + if (ownerEntry.Key == NetworkManager.LocalClientId) + { + continue; + } + if (ScenesLoaded.ContainsKey(clientSceneHandle)) + { + var scene = ScenesLoaded[clientSceneHandle]; + foreach (var networkObject in ownerEntry.Value) + { + SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); + networkObject.NetworkSceneHandle = sceneEntry.Key; + networkObject.SceneOriginHandle = scene.handle; + } + } + } + } + } + } + catch (Exception ex) + { + NetworkLog.LogErrorServer($"{ex.Message}\n Stack Trace:\n {ex.StackTrace}"); + } + + // Clear out the list once complete + //ObjectsMigratedIntoNewScene.Clear(); + } +#else internal void MigrateNetworkObjectsIntoScenes() { try @@ -2545,6 +3208,7 @@ internal void MigrateNetworkObjectsIntoScenes() // Clear out the list once complete ObjectsMigratedIntoNewScene.Clear(); } +#endif private List m_ScenesToRemoveFromObjectMigration = new List(); @@ -2554,12 +3218,71 @@ internal void MigrateNetworkObjectsIntoScenes() /// internal void CheckForAndSendNetworkObjectSceneChanged() { + +#if NGO_DAMODE // Early exit if not the server or there is nothing pending - if (!NetworkManager.IsServer || ObjectsMigratedIntoNewScene.Count == 0) + if (ObjectsMigratedIntoNewScene.Count == 0) { return; } + MigrateNetworkObjectsIntoScenes(); + + // Double check that the NetworkObjects to migrate still exist + m_ScenesToRemoveFromObjectMigration.Clear(); + foreach (var sceneEntry in ObjectsMigratedIntoNewScene) + { + if (!sceneEntry.Value.ContainsKey(NetworkManager.LocalClientId)) + { + continue; + } + var ownerSceneEntry = sceneEntry.Value[NetworkManager.LocalClientId]; + for (int i = sceneEntry.Value[NetworkManager.LocalClientId].Count - 1; i >= 0; i--) + { + // Remove NetworkObjects that are no longer spawned + if (!sceneEntry.Value[NetworkManager.LocalClientId][i].IsSpawned) + { + sceneEntry.Value[NetworkManager.LocalClientId].RemoveAt(i); + } + } + // If the scene entry no longer has any NetworkObjects to migrate + // then add it to the list of scenes to be removed from the table + // of scenes containing NetworkObjects to migrate. + if (sceneEntry.Value.Count == 0) + { + m_ScenesToRemoveFromObjectMigration.Add(sceneEntry.Key); + } + } + + // Remove owner sceneHandle entries that no longer have any NetworkObjects remaining + foreach (var sceneHandle in m_ScenesToRemoveFromObjectMigration) + { + ObjectsMigratedIntoNewScene[sceneHandle].Remove(NetworkManager.LocalClientId); + } + + var localOwnerHasEntries = false; + + foreach (var sceneEntry in ObjectsMigratedIntoNewScene) + { + if (sceneEntry.Value.ContainsKey(NetworkManager.LocalClientId)) + { + localOwnerHasEntries = true; + break; + } + } + + // If the local owner has no entries, then exit + if (!localOwnerHasEntries) + { + ObjectsMigratedIntoNewScene.Clear(); + return; + } +#else + // Early exit if not the server or there is nothing pending + if (!NetworkManager.IsServer || ObjectsMigratedIntoNewScene.Count == 0) + { + return; + } // Double check that the NetworkObjects to migrate still exist m_ScenesToRemoveFromObjectMigration.Clear(); foreach (var sceneEntry in ObjectsMigratedIntoNewScene) @@ -2592,26 +3315,48 @@ internal void CheckForAndSendNetworkObjectSceneChanged() { return; } - +#endif // Some NetworkObjects still exist, send the message var sceneEvent = BeginSceneEvent(); sceneEvent.SceneEventType = SceneEventType.ObjectSceneChanged; +#if NGO_DAMODE + SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); + ObjectsMigratedIntoNewScene.Clear(); +#else SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray()); +#endif + EndSceneEvent(sceneEvent.SceneEventId); + + } +#if NGO_DAMODE // Used to handle client-side scene migration messages received while // a client is synchronizing + internal struct DeferredObjectsMovedEvent + { + internal ulong OwnerId; + internal Dictionary> ObjectsMigratedTable; + } + internal List DeferredObjectsMovedEvents = new List(); +#else internal struct DeferredObjectsMovedEvent { internal Dictionary> ObjectsMigratedTable; } internal List DeferredObjectsMovedEvents = new List(); +#endif internal struct DeferredObjectCreation { internal ulong SenderId; internal uint MessageSize; +#if NGO_DAMODE + // When we transfer session owner and we are using a DAHost, this will be pertinent (otherwise it is not when connected to a DA service) + internal ulong[] ObserverIds; + internal ulong[] NewObserverIds; +#endif internal NetworkObject.SceneObject SceneObject; internal FastBufferReader FastBufferReader; } @@ -2619,12 +3364,21 @@ internal struct DeferredObjectCreation internal List DeferredObjectCreationList = new List(); internal int DeferredObjectCreationCount; +#if NGO_DAMODE + // The added clientIds is specific to DAHost when session ownership changes and a normal client is controlling scene loading + internal void DeferCreateObject(ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader fastBufferReader, ulong[] observerIds, ulong[] newObserverIds) +#else internal void DeferCreateObject(ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader fastBufferReader) +#endif { var deferredObjectCreationEntry = new DeferredObjectCreation() { SenderId = senderId, MessageSize = messageSize, +#if NGO_DAMODE + ObserverIds = observerIds, + NewObserverIds = newObserverIds, +#endif SceneObject = sceneObject, }; @@ -2645,12 +3399,97 @@ private void ProcessDeferredCreateObjectMessages() } var networkManager = NetworkManager; // Process all deferred create object messages. - foreach (var deferredObjectCreation in DeferredObjectCreationList) + for (int i = 0; i < DeferredObjectCreationList.Count; i++) { + var deferredObjectCreation = DeferredObjectCreationList[i]; +#if NGO_DAMODE + CreateObjectMessage.CreateObject(ref networkManager, ref deferredObjectCreation); +#else CreateObjectMessage.CreateObject(ref networkManager, deferredObjectCreation.SenderId, deferredObjectCreation.MessageSize, deferredObjectCreation.SceneObject, deferredObjectCreation.FastBufferReader); +#endif } DeferredObjectCreationCount = DeferredObjectCreationList.Count; DeferredObjectCreationList.Clear(); } + + public enum MapTypes + { + ServerToClient, + ClientToServer + } + public struct SceneMap : INetworkSerializable + { + public MapTypes MapType; + public Scene Scene; + public bool ScenePresent; + public string SceneName; + public int ServerHandle; + public int MappedLocalHandle; + public int LocalHandle; + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref MapType); + serializer.SerializeValue(ref ScenePresent); + if (serializer.IsReader) + { + SceneName = "Not Present"; + } + if (ScenePresent) + { + serializer.SerializeValue(ref SceneName); + serializer.SerializeValue(ref LocalHandle); + + } + serializer.SerializeValue(ref ServerHandle); + serializer.SerializeValue(ref MappedLocalHandle); + } + } + + public List GetSceneMapping(MapTypes mapType) + { + var mapping = new List(); + if (mapType == MapTypes.ServerToClient) + { + foreach (var entry in ServerSceneHandleToClientSceneHandle) + { + var scene = ScenesLoaded[entry.Value]; + var sceneIsPresent = scene.IsValid() && scene.isLoaded; + var sceneMap = new SceneMap() + { + MapType = mapType, + ServerHandle = entry.Key, + MappedLocalHandle = entry.Value, + LocalHandle = scene.handle, + Scene = scene, + ScenePresent = sceneIsPresent, + SceneName = sceneIsPresent ? scene.name : "NotPresent", + }; + mapping.Add(sceneMap); + } + } + else + { + foreach (var entry in ClientSceneHandleToServerSceneHandle) + { + var scene = ScenesLoaded[entry.Key]; + var sceneIsPresent = scene.IsValid() && scene.isLoaded; + var sceneMap = new SceneMap() + { + MapType = mapType, + ServerHandle = entry.Value, + MappedLocalHandle = entry.Key, + LocalHandle = scene.handle, + Scene = scene, + ScenePresent = sceneIsPresent, + SceneName = sceneIsPresent ? scene.name : "NotPresent", + }; + mapping.Add(sceneMap); + } + } + + return mapping; + } + } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 612b834d50..cbcb736e98 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Unity.Collections; using UnityEngine.SceneManagement; @@ -114,7 +115,10 @@ internal class SceneEventData : IDisposable /// Only used for scene events, this assures permissions when writing /// NetworkVariable information. If that process changes, then we need to update this + /// In distributed authority mode this is used to route messages to the appropriate destination client internal ulong TargetClientId; + /// Only used with a DAHost + internal ulong SenderClientId; private Dictionary> m_SceneNetworkObjects; private Dictionary m_SceneNetworkObjectDataOffsets; @@ -243,6 +247,9 @@ internal void InitializeForSynch() { SceneHandlesToSynchronize.Clear(); } +#if NGO_DAMODE + ForwardSynchronization = false; +#endif } /// @@ -334,7 +341,7 @@ internal void AddSpawnedNetworkObjects() // as well as the order in which they will be deserialized if (LogSerializationOrder && m_NetworkManager.LogLevel == LogLevel.Developer) { - var messageBuilder = new System.Text.StringBuilder(0xFFFF); + var messageBuilder = new StringBuilder(0xFFFF); messageBuilder.AppendLine("[Server-Side Client-Synchronization] NetworkObject serialization order:"); foreach (var networkObject in m_NetworkObjectsSync) { @@ -429,6 +436,38 @@ private int SortNetworkObjects(NetworkObject first, NetworkObject second) return 0; } + internal bool EnableSerializationLogs = false; + + private void LogArray(byte[] data, int start = 0, int stop = 0, StringBuilder builder = null) + { + var usingExternalBuilder = builder != null; + if (!usingExternalBuilder) + { + builder = new StringBuilder(); + } + + if (stop == 0) + { + stop = data.Length; + } + + builder.AppendLine($"[Start Data Dump][Start = {start}][Stop = {stop}] Size ({stop - start})"); + for (int i = start; i < stop; i++) + { + builder.Append($"{data[i]:X2} "); + } + builder.Append("\n"); + + if (!usingExternalBuilder) + { + UnityEngine.Debug.Log(builder.ToString()); + } + } + +#if NGO_DAMODE + internal bool ForwardSynchronization; +#endif + /// /// Client and Server Side: /// Serializes data based on the SceneEvent type () @@ -439,6 +478,14 @@ internal void Serialize(FastBufferWriter writer) // Write the scene event type writer.WriteValueSafe(SceneEventType); +#if NGO_DAMODE + if (m_NetworkManager.DistributedAuthorityMode) + { + BytePacker.WriteValueBitPacked(writer, TargetClientId); + BytePacker.WriteValueBitPacked(writer, SenderClientId); + } +#endif + if (SceneEventType == SceneEventType.ActiveSceneChanged) { writer.WriteValueSafe(ActiveSceneHash); @@ -473,12 +520,27 @@ internal void Serialize(FastBufferWriter writer) case SceneEventType.Synchronize: { writer.WriteValueSafe(ActiveSceneHash); + WriteSceneSynchronizationData(writer); + + if (EnableSerializationLogs) + { + LogArray(writer.ToArray(), 0, writer.Length); + } break; } case SceneEventType.Load: { - SerializeScenePlacedObjects(writer); +#if NGO_DAMODE + if (m_NetworkManager.DistributedAuthorityMode && IsForwarding && m_NetworkManager.DAHost) + { + CopyInternalBuffer(ref writer); + } + else +#endif + { + SerializeScenePlacedObjects(writer); + } break; } case SceneEventType.SynchronizeComplete: @@ -500,6 +562,13 @@ internal void Serialize(FastBufferWriter writer) } } +#if NGO_DAMODE + private unsafe void CopyInternalBuffer(ref FastBufferWriter writer) + { + writer.WriteBytesSafe(InternalBuffer.GetUnsafePtrAtCurrentPosition(), InternalBuffer.Length); + } +#endif + /// /// Server Side: /// Called at the end of a event once the scene is loaded and scene placed NetworkObjects @@ -507,13 +576,33 @@ internal void Serialize(FastBufferWriter writer) /// internal void WriteSceneSynchronizationData(FastBufferWriter writer) { + var builder = (StringBuilder)null; + if (EnableSerializationLogs) + { + builder = new StringBuilder(); + builder.AppendLine($"[Write][Synchronize-Start][WPos: {writer.Position}] Begin:"); + } // Write the scenes we want to load, in the order we want to load them writer.WriteValueSafe(ScenesToSynchronize.ToArray()); writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray()); - // Store our current position in the stream to come back and say how much data we have written var positionStart = writer.Position; +#if NGO_DAMODE + if (m_NetworkManager.DistributedAuthorityMode && ForwardSynchronization && m_NetworkManager.DAHost) + { + writer.WriteValueSafe(m_InternalBufferSize); + CopyInternalBuffer(ref writer); + if (EnableSerializationLogs) + { + LogArray(writer.ToArray(), positionStart); + } + return; + } +#endif + + + // Size Place Holder -- Start // !!NOTE!!: Since this is a placeholder to be set after we know how much we have written, // for stream offset purposes this MUST not be a packed value! @@ -522,15 +611,38 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) // Write the number of NetworkObjects we are serializing writer.WriteValueSafe(m_NetworkObjectsSync.Count); - + if (EnableSerializationLogs) + { + builder.AppendLine($"[Synchronize Objects][positionStart: {positionStart}][WPos: {writer.Position}][NO-Count: {m_NetworkObjectsSync.Count}] Begin:"); + } +#if NGO_DAMODE + var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; +#endif // Serialize all NetworkObjects that are spawned for (var i = 0; i < m_NetworkObjectsSync.Count; ++i) { + var networkObject = m_NetworkObjectsSync[i]; var noStart = writer.Position; +#if NGO_DAMODE + // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. + var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId, distributedAuthority); +#else var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId); +#endif sceneObject.Serialize(writer); var noStop = writer.Position; totalBytes += noStop - noStart; + if (EnableSerializationLogs) + { + var offStart = noStart - (positionStart + sizeof(int)); + var offStop = noStop - (positionStart + sizeof(int)); + builder.AppendLine($"[Head: {offStart}][Tail: {offStop}][Size: {offStop - offStart}][{networkObject.name}][NID-{networkObject.NetworkObjectId}][Children: {networkObject.ChildNetworkBehaviours.Count}]"); + LogArray(writer.ToArray(), noStart, noStop, builder); + } + } + if (EnableSerializationLogs) + { + UnityEngine.Debug.Log(builder.ToString()); } // Write the number of despawned in-scene placed NetworkObjects @@ -552,6 +664,10 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) // Write the total size written to the stream by NetworkObjects being serialized writer.WriteValueSafe(bytesWritten); writer.Seek(positionEnd); + if (EnableSerializationLogs) + { + LogArray(writer.ToArray(), positionStart); + } } /// @@ -608,6 +724,13 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) internal void Deserialize(FastBufferReader reader) { reader.ReadValueSafe(out SceneEventType); +#if NGO_DAMODE + if (m_NetworkManager.DistributedAuthorityMode) + { + ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId); + ByteUnpacker.ReadValueBitPacked(reader, out SenderClientId); + } +#endif if (SceneEventType == SceneEventType.ActiveSceneChanged) { reader.ReadValueSafe(out ActiveSceneHash); @@ -648,6 +771,10 @@ internal void Deserialize(FastBufferReader reader) case SceneEventType.Synchronize: { reader.ReadValueSafe(out ActiveSceneHash); + if (EnableSerializationLogs) + { + LogArray(reader.ToArray(), 0, reader.Length); + } CopySceneSynchronizationData(reader); break; } @@ -682,6 +809,10 @@ internal void Deserialize(FastBufferReader reader) } } +#if NGO_DAMODE + private int m_InternalBufferSize; +#endif + /// /// Client Side: /// Prepares for a scene synchronization event and copies the scene synchronization data @@ -698,6 +829,10 @@ internal void CopySceneSynchronizationData(FastBufferReader reader) // is not packed! reader.ReadValueSafe(out int sizeToCopy); +#if NGO_DAMODE + m_InternalBufferSize = sizeToCopy; +#endif + unsafe { if (!reader.TryBeginRead(sizeToCopy)) @@ -708,6 +843,10 @@ internal void CopySceneSynchronizationData(FastBufferReader reader) m_HasInternalBuffer = true; // We use Allocator.Persistent since scene synchronization will most likely take longer than 4 frames InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, sizeToCopy); + if (EnableSerializationLogs) + { + LogArray(InternalBuffer.ToArray()); + } } } @@ -957,12 +1096,24 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() /// internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) { + var builder = (StringBuilder)null; + if (EnableSerializationLogs) + { + builder = new StringBuilder(); + } + try { // Process all spawned NetworkObjects for this network session InternalBuffer.ReadValueSafe(out int newObjectsCount); + if (EnableSerializationLogs) + { + builder.AppendLine($"[Read][Synchronize Objects][WPos: {InternalBuffer.Position}][NO-Count: {newObjectsCount}] Begin:"); + } + for (int i = 0; i < newObjectsCount; i++) { + var noStart = InternalBuffer.Position; var sceneObject = new NetworkObject.SceneObject(); sceneObject.Deserialize(InternalBuffer); @@ -973,6 +1124,12 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) } var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager); + var noStop = InternalBuffer.Position; + if (EnableSerializationLogs) + { + builder.AppendLine($"[Head: {noStart}][Tail: {noStop}][Size: {noStop - noStart}][{spawnedNetworkObject.name}][NID-{spawnedNetworkObject.NetworkObjectId}][Children: {spawnedNetworkObject.ChildNetworkBehaviours.Count}]"); + LogArray(InternalBuffer.ToArray(), noStart, noStop, builder); + } // If we failed to deserialize the NetowrkObject then don't add null to the list if (spawnedNetworkObject != null) { @@ -982,11 +1139,20 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) } } } + if (EnableSerializationLogs) + { + UnityEngine.Debug.Log(builder.ToString()); + } // Now deserialize the despawned in-scene placed NetworkObjects list (if any) DeserializeDespawnedInScenePlacedNetworkObjects(); } + catch (Exception ex) + { + UnityEngine.Debug.LogException(ex); + UnityEngine.Debug.Log(builder.ToString()); + } finally { InternalBuffer.Dispose(); @@ -1040,6 +1206,42 @@ internal void ReadSceneEventProgressDone(FastBufferReader reader) /// Serialize scene handles and associated NetworkObjects that were migrated /// into a new scene. /// +#if NGO_DAMODE + internal bool IsForwarding; + private ulong m_OwnerId; + + + private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) + { + var sceneManager = m_NetworkManager.SceneManager; + var ownerId = m_NetworkManager.LocalClientId; + if (IsForwarding) + { + ownerId = m_OwnerId; + } + + // Write the owner identifier + writer.WriteValueSafe(ownerId); + + // Write the number of scene handles + writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count); + foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene) + { + if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandleObjects.Key].ContainsKey(ownerId)) + { + throw new Exception($"Trying to send object scene migration for Client-{ownerId} but the client has no entries to send!"); + } + // Write the scene handle + writer.WriteValueSafe(sceneHandleObjects.Key); + // Write the number of NetworkObjectIds to expect + writer.WriteValueSafe(sceneHandleObjects.Value[ownerId].Count); + foreach (var networkObject in sceneHandleObjects.Value[ownerId]) + { + writer.WriteValueSafe(networkObject.NetworkObjectId); + } + } + } +#else private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) { var sceneManager = m_NetworkManager.SceneManager; @@ -1059,6 +1261,7 @@ private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) // Once we are done, clear the table sceneManager.ObjectsMigratedIntoNewScene.Clear(); } +#endif /// /// Deserialize scene handles and associated NetworkObjects that need to @@ -1068,12 +1271,49 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; var spawnManager = m_NetworkManager.SpawnManager; +#if !NGO_DAMODE // Just always assure this has no entries sceneManager.ObjectsMigratedIntoNewScene.Clear(); +#endif var numberOfScenes = 0; var sceneHandle = 0; var objectCount = 0; var networkObjectId = (ulong)0; + +#if NGO_DAMODE + var ownerID = (ulong)0; + reader.ReadValueSafe(out ownerID); + m_OwnerId = ownerID; + reader.ReadValueSafe(out numberOfScenes); + + for (int i = 0; i < numberOfScenes; i++) + { + reader.ReadValueSafe(out sceneHandle); + if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(sceneHandle)) + { + sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, new Dictionary>()); + } + + if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].ContainsKey(ownerID)) + { + sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(ownerID, new List()); + } + + reader.ReadValueSafe(out objectCount); + for (int j = 0; j < objectCount; j++) + { + reader.ReadValueSafe(out networkObjectId); + if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!"); + continue; + } + var networkObject = spawnManager.SpawnedObjects[networkObjectId]; + // Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed + sceneManager.ObjectsMigratedIntoNewScene[sceneHandle][ownerID].Add(networkObject); + } + } +#else reader.ReadValueSafe(out numberOfScenes); for (int i = 0; i < numberOfScenes; i++) { @@ -1093,6 +1333,7 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(spawnManager.SpawnedObjects[networkObjectId]); } } +#endif } @@ -1102,6 +1343,83 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) /// has completed synchronization to assure the associated NetworkObjects being /// migrated to a new scene are instantiated and spawned. /// +#if NGO_DAMODE + private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) + { + var sceneManager = m_NetworkManager.SceneManager; + var spawnManager = m_NetworkManager.SpawnManager; + var ownerId = (ulong)0; + var numberOfScenes = 0; + var sceneHandle = 0; + var objectCount = 0; + var networkObjectId = (ulong)0; + + reader.ReadValueSafe(out ownerId); + + + var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent() + { + OwnerId = ownerId, + ObjectsMigratedTable = new Dictionary>(), + }; + + + reader.ReadValueSafe(out numberOfScenes); + for (int i = 0; i < numberOfScenes; i++) + { + reader.ReadValueSafe(out sceneHandle); + deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List()); + reader.ReadValueSafe(out objectCount); + for (int j = 0; j < objectCount; j++) + { + reader.ReadValueSafe(out networkObjectId); + deferredObjectsMovedEvent.ObjectsMigratedTable[sceneHandle].Add(networkObjectId); + } + } + sceneManager.DeferredObjectsMovedEvents.Add(deferredObjectsMovedEvent); + } + + internal void ProcessDeferredObjectSceneChangedEvents() + { + var sceneManager = m_NetworkManager.SceneManager; + var spawnManager = m_NetworkManager.SpawnManager; + if (sceneManager.DeferredObjectsMovedEvents.Count == 0) + { + return; + } + foreach (var objectsMovedEvent in sceneManager.DeferredObjectsMovedEvents) + { + foreach (var keyEntry in objectsMovedEvent.ObjectsMigratedTable) + { + if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(keyEntry.Key)) + { + sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, new Dictionary>()); + } + if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].ContainsKey(objectsMovedEvent.OwnerId)) + { + sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(objectsMovedEvent.OwnerId, new List()); + } + + foreach (var objectId in keyEntry.Value) + { + if (!spawnManager.SpawnedObjects.ContainsKey(objectId)) + { + NetworkLog.LogWarning($"[Deferred][Object Scene Migration] Trying to synchronize NetworkObjectId ({objectId}) but it was not spawned or no longer exists!"); + continue; + } + var networkObject = spawnManager.SpawnedObjects[objectId]; + if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key][objectsMovedEvent.OwnerId].Contains(networkObject)) + { + sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key][objectsMovedEvent.OwnerId].Add(networkObject); + } + } + } + objectsMovedEvent.ObjectsMigratedTable.Clear(); + } + + sceneManager.DeferredObjectsMovedEvents.Clear(); + } +#else private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; @@ -1172,6 +1490,8 @@ internal void ProcessDeferredObjectSceneChangedEvents() sceneManager.MigrateNetworkObjectsIntoScenes(); } } +#endif + /// /// Used to release the pooled network buffer diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index d50c6c73ea..4c6b9e3f92 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -55,6 +55,12 @@ public enum SceneEventProgressStatus /// This is returned when a client attempts to perform a server only action /// ServerOnlyAction, +#if NGO_DAMODE + /// + /// This is returned when a client that is not the session owner attempts to perform a session owner only action + /// + SessionOwnerOnlyAction, +#endif } /// @@ -157,22 +163,30 @@ internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressSta if (status == SceneEventProgressStatus.Started) { m_NetworkManager = networkManager; - +#if NGO_DAMODE + WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut; + if ((networkManager.IsServer && !networkManager.DistributedAuthorityMode) || (networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner)) + { +#else if (networkManager.IsServer) { + WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut; +#endif m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; // Track the clients that were connected when we started this event foreach (var connectedClientId in networkManager.ConnectedClientsIds) { - // Ignore the host client +#if NGO_DAMODE + // Ignore the host or session owner + if ((!networkManager.DistributedAuthorityMode && NetworkManager.ServerClientId == connectedClientId) || (networkManager.DistributedAuthorityMode && networkManager.CurrentSessionOwner == connectedClientId)) +#else if (NetworkManager.ServerClientId == connectedClientId) +#endif { continue; } ClientsProcessingSceneEvent.Add(connectedClientId, false); } - - WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut; m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress()); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index 8adcc98790..c2cfc133bd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -245,6 +245,11 @@ public unsafe FastBufferReader(FastBufferReader reader, Allocator copyAllocator, /// public unsafe void Dispose() { + if (Handle == null) + { + return; + } + UnsafeUtility.Free(Handle, Handle->Allocator); Handle = null; } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index aa50ad6a9a..a6865400b7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -772,7 +772,11 @@ public unsafe void WriteBytes(NativeArray value, int size = -1, int offset [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytes(NativeList value, int size = -1, int offset = 0) { +#if UTP_TRANSPORT_2_0_ABOVE + byte* ptr = value.GetUnsafePtr(); +#else byte* ptr = (byte*)value.GetUnsafePtr(); +#endif WriteBytes(ptr, size == -1 ? value.Length : size, offset); } @@ -816,7 +820,11 @@ public unsafe void WriteBytesSafe(NativeArray value, int size = -1, int of [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytesSafe(NativeList value, int size = -1, int offset = 0) { +#if UTP_TRANSPORT_2_0_ABOVE + byte* ptr = value.GetUnsafePtr(); +#else byte* ptr = (byte*)value.GetUnsafePtr(); +#endif WriteBytesSafe(ptr, size == -1 ? value.Length : size, offset); } @@ -985,7 +993,11 @@ internal unsafe void WriteUnmanagedSafe(NativeArray value) where T : unman internal unsafe void WriteUnmanaged(NativeList value) where T : unmanaged { WriteUnmanaged(value.Length); +#if UTP_TRANSPORT_2_0_ABOVE + var ptr = value.GetUnsafePtr(); +#else var ptr = (T*)value.GetUnsafePtr(); +#endif { byte* bytes = (byte*)ptr; WriteBytes(bytes, sizeof(T) * value.Length); @@ -995,7 +1007,11 @@ internal unsafe void WriteUnmanaged(NativeList value) where T : unmanaged internal unsafe void WriteUnmanagedSafe(NativeList value) where T : unmanaged { WriteUnmanagedSafe(value.Length); +#if UTP_TRANSPORT_2_0_ABOVE + var ptr = value.GetUnsafePtr(); +#else var ptr = (T*)value.GetUnsafePtr(); +#endif { byte* bytes = (byte*)ptr; WriteBytesSafe(bytes, sizeof(T) * value.Length); @@ -1193,7 +1209,11 @@ public void WriteValue(NativeList value, ForGeneric unused = default) wher [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteValueSafe(NativeHashSet value) where T : unmanaged, IEquatable { +#if UTP_TRANSPORT_2_0_ABOVE + WriteUnmanagedSafe(value.Count); +#else WriteUnmanagedSafe(value.Count()); +#endif foreach (var item in value) { var iReffable = item; @@ -1206,7 +1226,11 @@ internal void WriteValueSafe(NativeHashMap value) where TKey : unmanaged, IEquatable where TVal : unmanaged { +#if UTP_TRANSPORT_2_0_ABOVE + WriteUnmanagedSafe(value.Count); +#else WriteUnmanagedSafe(value.Count()); +#endif foreach (var item in value) { (var key, var val) = (item.Key, item.Value); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs index 6df91b8f55..ab2a88600f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs @@ -40,7 +40,7 @@ public NetworkBehaviourReference(NetworkBehaviour networkBehaviour) /// True if the was found; False if the was not found. This can happen if the corresponding has not been spawned yet. you can try getting the reference at a later point in time. public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager networkManager = null) { - networkBehaviour = GetInternal(this, null); + networkBehaviour = GetInternal(this, networkManager); return networkBehaviour != null; } @@ -53,7 +53,7 @@ public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager network /// True if the was found; False if the was not found. This can happen if the corresponding has not been spawned yet. you can try getting the reference at a later point in time. public bool TryGet(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour { - networkBehaviour = GetInternal(this, null) as T; + networkBehaviour = GetInternal(this, networkManager) as T; return networkBehaviour != null; } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 45b3dff945..9eb34ceb68 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using UnityEngine; namespace Unity.Netcode @@ -13,6 +14,8 @@ public class NetworkSpawnManager // Stores the objects that need to be shown at end-of-frame internal Dictionary> ObjectsToShowToClient = new Dictionary>(); + internal Dictionary> ClientsToShowObject = new Dictionary>(); + /// /// The currently spawned objects /// @@ -40,6 +43,107 @@ public class NetworkSpawnManager /// private Dictionary m_ObjectToOwnershipTable = new Dictionary(); +#if NGO_DAMODE + /// + /// In distributed authority mode, a list of known spawned player NetworkObject instance is maintained by each client. + /// + public IReadOnlyList PlayerObjects => m_PlayerObjects; + // Since NetworkSpawnManager is destroyed when NetworkManager shuts down, it will always be an empty list for each new network session. + // DANGO-TODO: We need to add something like a ConnectionStateMessage that is sent by either the DAHost or CMBService to each client when a client + // is connected and synchronized or when a cient disconnects (but the player objects list we should keep as it is useful to have). + private List m_PlayerObjects = new List(); + + private Dictionary> m_PlayerObjectsTable = new Dictionary>(); + + public List GetConnectedPlayers() + { + return m_PlayerObjectsTable.Keys.ToList(); + } + + /// + /// Adds a player object and updates all other players' observers list + /// + private void AddPlayerObject(NetworkObject playerObject) + { + if (!playerObject.IsPlayerObject) + { + if (NetworkManager.LogLevel == LogLevel.Normal) + { + NetworkLog.LogError($"Attempting to register a {nameof(NetworkObject)} as a player object but {nameof(NetworkObject.IsPlayerObject)} is not set!"); + return; + } + } + foreach (var player in m_PlayerObjects) + { + player.Observers.Add(playerObject.OwnerClientId); + playerObject.Observers.Add(player.OwnerClientId); + } + m_PlayerObjects.Add(playerObject); + if (!m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId)) + { + m_PlayerObjectsTable.Add(playerObject.OwnerClientId, new List()); + } + m_PlayerObjectsTable[playerObject.OwnerClientId].Add(playerObject); + } + + internal void UpdateNetworkClientPlayer(NetworkObject playerObject) + { + // If the player's client does not already have a NetworkClient entry + if (!NetworkManager.ConnectionManager.ConnectedClients.ContainsKey(playerObject.OwnerClientId)) + { + // Add the player's client + NetworkManager.ConnectionManager.AddClient(playerObject.OwnerClientId); + } + var playerNetworkClient = NetworkManager.ConnectionManager.ConnectedClients[playerObject.OwnerClientId]; + + // If a client changes their player object, then we should adjust for the client's new player + if (playerNetworkClient.PlayerObject != null && m_PlayerObjects.Contains(playerNetworkClient.PlayerObject)) + { + // Just remove the previous player object but keep the assigned observers of the NetworkObject + RemovePlayerObject(playerNetworkClient.PlayerObject, true); + } + // Now update the associated NetworkClient's player object + NetworkManager.ConnectionManager.ConnectedClients[playerObject.OwnerClientId].AssignPlayerObject(ref playerObject); + AddPlayerObject(playerObject); + } + + /// + /// Removes a player object and updates all other players' observers list + /// + private void RemovePlayerObject(NetworkObject playerObject, bool keepObservers = false) + { + if (!playerObject.IsPlayerObject) + { + if (NetworkManager.LogLevel == LogLevel.Normal) + { + NetworkLog.LogError($"Attempting to deregister a {nameof(NetworkObject)} as a player object but {nameof(NetworkObject.IsPlayerObject)} is not set!"); + return; + } + } + playerObject.IsPlayerObject = false; + m_PlayerObjects.Remove(playerObject); + if (m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId)) + { + m_PlayerObjectsTable[playerObject.OwnerClientId].Remove(playerObject); + if (m_PlayerObjectsTable[playerObject.OwnerClientId].Count == 0) + { + m_PlayerObjectsTable.Remove(playerObject.OwnerClientId); + } + } + + // If we want to keep the observers, then exit early + if (keepObservers) + { + return; + } + + foreach (var player in m_PlayerObjects) + { + player.Observers.Remove(playerObject.OwnerClientId); + } + } +#endif + internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId) { if (!ObjectsToShowToClient.ContainsKey(clientId)) @@ -47,11 +151,34 @@ internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId ObjectsToShowToClient.Add(clientId, new List()); } ObjectsToShowToClient[clientId].Add(networkObject); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + if (!ClientsToShowObject.ContainsKey(networkObject)) + { + ClientsToShowObject.Add(networkObject, new List()); + } + ClientsToShowObject[networkObject].Add(clientId); + } +#endif } // returns whether any matching objects would have become visible and were returned to hidden state internal bool RemoveObjectFromShowingTo(NetworkObject networkObject, ulong clientId) { +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + if (ClientsToShowObject.ContainsKey(networkObject)) + { + ClientsToShowObject[networkObject].Remove(clientId); + if (ClientsToShowObject[networkObject].Count == 0) + { + ClientsToShowObject.Remove(networkObject); + } + } + } +#endif var ret = false; if (!ObjectsToShowToClient.ContainsKey(clientId)) { @@ -95,6 +222,13 @@ internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, } else { +#if NGO_DAMODE + // If we already had this owner in our table then just exit + if (NetworkManager.DistributedAuthorityMode && previousOwner == newOwner) + { + return; + } +#endif m_ObjectToOwnershipTable[networkObject.NetworkObjectId] = newOwner; } } @@ -142,7 +276,7 @@ internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, { OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId); } - else if (NetworkManager.LogLevel == LogLevel.Developer) + else if (NetworkManager.LogLevel == LogLevel.Developer && previousOwner == newOwner) { NetworkLog.LogWarning($"Setting ownership twice? Client-ID {previousOwner} already owns NetworkObject ID {networkObject.NetworkObjectId}!"); } @@ -182,7 +316,12 @@ internal ulong GetNetworkObjectId() m_NetworkObjectIdCounter++; +#if NGO_DAMODE + // DANGO-TODO: Need a more robust solution here. + return m_NetworkObjectIdCounter + (NetworkManager.LocalClientId * 10000); +#else return m_NetworkObjectIdCounter; +#endif } /// @@ -194,7 +333,21 @@ public NetworkObject GetLocalPlayerObject() return GetPlayerNetworkObject(NetworkManager.LocalClientId); } - +#if NGO_DAMODE + /// + /// Returns all instances assigned to the client identifier + /// + /// the client identifier of the player + /// A list of instances (if more than one are assigned) + public List GetPlayerNetworkObjects(ulong clientId) + { + if (m_PlayerObjectsTable.ContainsKey(clientId)) + { + return m_PlayerObjectsTable[clientId]; + } + return null; + } +#endif /// /// Returns the player object with a given clientId or null if one does not exist. This is only valid server side. /// @@ -202,16 +355,35 @@ public NetworkObject GetLocalPlayerObject() /// The player object with a given clientId or null if one does not exist public NetworkObject GetPlayerNetworkObject(ulong clientId) { +#if NGO_DAMODE + if (!NetworkManager.DistributedAuthorityMode) + { + if (!NetworkManager.IsServer && NetworkManager.LocalClientId != clientId) + { + throw new NotServerException("Only the server can find player objects from other clients."); + } + if (TryGetNetworkClient(clientId, out NetworkClient networkClient)) + { + return networkClient.PlayerObject; + } + } + else + { + if (m_PlayerObjectsTable.ContainsKey(clientId)) + { + return m_PlayerObjectsTable[clientId].First(); + } + } +#else if (!NetworkManager.IsServer && NetworkManager.LocalClientId != clientId) { throw new NotServerException("Only the server can find player objects from other clients."); } - if (TryGetNetworkClient(clientId, out NetworkClient networkClient)) { return networkClient.PlayerObject; } - +#endif return null; } @@ -240,24 +412,126 @@ private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient return false; } +#if NGO_DAMODE + protected virtual void InternalOnOwnershipChanged(ulong perviousOwner, ulong newOwner) + { + + } +#endif internal void RemoveOwnership(NetworkObject networkObject) { +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode && !NetworkManager.ShutdownInProgress) + { + if (networkObject.IsOwnershipDistributable || networkObject.IsOwnershipTransferable) + { + if (networkObject.IsOwner || NetworkManager.DAHost) + { + NetworkLog.LogWarning("DANGO-TODO: Determine if removing ownership should make the CMB Service redistribute ownership or if this just isn't a valid thing in DAMode."); + return; + } + else + { + NetworkLog.LogError($"Only the owner is allowed to remove ownership in distributed authority mode!"); + return; + } + } + else + { + if (!NetworkManager.DAHost) + { + Debug.LogError($"Only {nameof(NetworkObject)}s with {nameof(NetworkObject.IsOwnershipDistributable)} or {nameof(NetworkObject.IsOwnershipTransferable)} set can perform ownership changes!"); + } + return; + } + } + ChangeOwnership(networkObject, NetworkManager.ServerClientId, true); +#else ChangeOwnership(networkObject, NetworkManager.ServerClientId); +#endif + } + +#if !NGO_DAMODE internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) { if (!NetworkManager.IsServer) { throw new NotServerException("Only the server can change ownership"); } - +#else + internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool isAuthorized, bool isRequestApproval = false) + { + if (NetworkManager.DistributedAuthorityMode) + { + // If are not authorized and this is not an approved ownership change, then check to see if we can change ownership + if (!isAuthorized && !isRequestApproval) + { + if (networkObject.IsOwnershipLocked) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogErrorServer($"[{networkObject.name}][Locked] You cannot change ownership while a {nameof(NetworkObject)} is locked!"); + } + networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.Locked); + return; + } + if (networkObject.IsRequestInProgress) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogErrorServer($"[{networkObject.name}][Request Pending] You cannot change ownership while a {nameof(NetworkObject)} has a pending ownership request!"); + } + networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.RequestInProgress); + return; + } + if (networkObject.IsOwnershipRequestRequired) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogErrorServer($"[{networkObject.name}][Request Required] You cannot change ownership directly if a {nameof(NetworkObject)} has the {NetworkObject.OwnershipStatus.RequestRequired} flag set!"); + } + networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.RequestRequired); + return; + } + if (!networkObject.IsOwnershipTransferable) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogErrorServer($"[{networkObject.name}][Not transferrable] You cannot change ownership of a {nameof(NetworkObject)} that does not have the {NetworkObject.OwnershipStatus.Transferable} flag set!"); + } + networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.NotTransferrable); + return; + } + } + } + else if (!isAuthorized) + { + throw new NotServerException("Only the server can change ownership"); + } +#endif if (!networkObject.IsSpawned) { throw new SpawnStateException("Object is not spawned"); } +#if NGO_DAMODE + if (networkObject.OwnerClientId == clientId && networkObject.PreviousOwnerId == clientId) + { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarningServer($"[Already Owner] Unnecessary ownership change for {networkObject.name} as it is already the owned by client-{clientId}"); + } + return; + } + + // Used to distinguish whether a new owner should receive any currently dirty NetworkVariable updates + networkObject.PreviousOwnerId = networkObject.OwnerClientId; +#else var previous = networkObject.OwnerClientId; +#endif + // Assign the new owner networkObject.OwnerClientId = clientId; @@ -267,18 +541,76 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) networkObject.MarkVariablesDirty(true); NetworkManager.BehaviourUpdater.AddForUpdate(networkObject); - // Server adds entries for all client ownership + // Authority adds entries for all client ownership UpdateOwnershipTable(networkObject, networkObject.OwnerClientId); // Always notify locally on the server when a new owner is assigned networkObject.InvokeBehaviourOnGainedOwnership(); +#if NGO_DAMODE + var size = 0; + if (NetworkManager.DistributedAuthorityMode) + { + var message = new ChangeOwnershipMessage + { + NetworkObjectId = networkObject.NetworkObjectId, + OwnerClientId = networkObject.OwnerClientId, + DistributedAuthorityMode = NetworkManager.DistributedAuthorityMode, + RequestApproved = isRequestApproval, + OwnershipIsChanging = true, + RequestClientId = networkObject.PreviousOwnerId, + OwnershipFlags = (ushort)networkObject.Ownership, + }; + // If we are connected to the CMB service or not the DAHost (i.e. pure DA-Clients only) + if (NetworkManager.CMBServiceConnection || !NetworkManager.DAHost) + { + // Always update the network properties in distributed authority mode for the client gaining ownership + for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) + { + networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); + } + + size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ServerClientId); + NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(NetworkManager.LocalClientId, networkObject, size); + } + else // We are the DAHost so broadcast the ownership change + { + foreach (var client in NetworkManager.ConnectedClients) + { + if (client.Value.ClientId == NetworkManager.ServerClientId) + { + continue; + } + if (networkObject.IsNetworkVisibleTo(client.Value.ClientId)) + { + size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId); + NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); + } + } + } + } + else // Normal Client-Server mode + { + var message = new ChangeOwnershipMessage + { + NetworkObjectId = networkObject.NetworkObjectId, + OwnerClientId = networkObject.OwnerClientId, + }; + foreach (var client in NetworkManager.ConnectedClients) + { + if (networkObject.IsNetworkVisibleTo(client.Value.ClientId)) + { + size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId); + NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); + } + } + } +#else var message = new ChangeOwnershipMessage { NetworkObjectId = networkObject.NetworkObjectId, - OwnerClientId = networkObject.OwnerClientId + OwnerClientId = networkObject.OwnerClientId, }; - foreach (var client in NetworkManager.ConnectedClients) { if (networkObject.IsNetworkVisibleTo(client.Value.ClientId)) @@ -287,12 +619,16 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); } } - +#endif // After we have sent the change ownership message to all client observers, invoke the ownership changed notification. /// !!Important!! /// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership /// change can be sent from NetworkBehaviours that override the +#if NGO_DAMODE + networkObject.InvokeOwnershipChanged(networkObject.PreviousOwnerId, clientId); +#else networkObject.InvokeOwnershipChanged(previous, clientId); +#endif } internal bool HasPrefab(NetworkObject.SceneObject sceneObject) @@ -367,7 +703,13 @@ public NetworkObject InstantiateAndSpawn(NetworkObject networkPrefab, ulong owne return null; } +#if NGO_DAMODE + ownerClientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; + // We only need to check for authority when running in client-server mode + if (!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) +#else if (!NetworkManager.IsServer) +#endif { Debug.LogError(InstantiateAndSpawnErrors[InstantiateAndSpawnErrorTypes.NotAuthority]); return null; @@ -394,10 +736,14 @@ public NetworkObject InstantiateAndSpawn(NetworkObject networkPrefab, ulong owne /// internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networkPrefab, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default) { - var networkObject = networkPrefab; // Host spawns the ovveride and server spawns the original prefab unless forceOverride is set to true where both server or host will spawn the override. +#if NGO_DAMODE + // In distributed authority mode, we alaways get the override + if (forceOverride || NetworkManager.IsHost || NetworkManager.DistributedAuthorityMode) +#else if (forceOverride || NetworkManager.IsHost) +#endif { networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation); } @@ -509,7 +855,6 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO else // Get the in-scene placed NetworkObject { networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, sceneObject.NetworkSceneHandle); - if (networkObject == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) @@ -530,6 +875,10 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO { networkObject.DestroyWithScene = sceneObject.DestroyWithScene; networkObject.NetworkSceneHandle = sceneObject.NetworkSceneHandle; +#if NGO_DAMODE + networkObject.DontDestroyWithOwner = sceneObject.DontDestroyWithOwner; + networkObject.Ownership = (NetworkObject.OwnershipStatus)sceneObject.OwnershipFlags; +#endif // SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject) // This is a special case scenario where a late joining client has joined and loaded one or @@ -604,7 +953,19 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO return networkObject; } - // Ran on both server and client + /// + /// Invoked from: + /// - ConnectionManager after instantiating a player prefab when running in client-server. + /// - NetworkObject when spawning a newly instantiated NetworkObject for the first time. + /// - NetworkSceneManager after a server/session-owner has loaded a scene to locally spawn the newly instantiated in-scene placed NetworkObjects. + /// - NetworkSpawnManager when spawning any already loaded in-scene placed NetworkObjects (client-server or session owner). + /// + /// Client-Server: + /// Server is the only instance that invokes this method. + /// + /// Distributed Authority: + /// DAHost client and standard DA clients invoke this method. + /// internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) { if (networkObject == null) @@ -614,7 +975,12 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo if (networkObject.IsSpawned) { +#if NGO_DAMODE + Debug.LogError($"{networkObject.name} is already spawned!"); + return; +#else throw new SpawnStateException("Object is already spawned"); +#endif } if (!sceneObject) @@ -626,10 +992,53 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo } } +#if NGO_DAMODE + // DANGO-TODO: It would be nice to allow users to specify which clients are observers prior to spawning + // For now, this is the best place I could find to add all connected clients as observers for newly + // instantiated and spawned NetworkObjects on the authoritative side. + if (NetworkManager.DistributedAuthorityMode) + { + if (NetworkManager.NetworkConfig.EnableSceneManagement && sceneObject) + { + networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; + networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; + } + + // Always add the owner/authority even if SpawnWithObservers is false + // (authority should not take into consideration networkObject.CheckObjectVisibility when SpawnWithObservers is false) + if (!networkObject.SpawnWithObservers) + { + networkObject.Observers.Add(ownerClientId); + } + else + { + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + // If SpawnWithObservers is enabled, then authority does take networkObject.CheckObjectVisibility into consideration + if (networkObject.CheckObjectVisibility != null && !networkObject.CheckObjectVisibility.Invoke(clientId)) + { + continue; + } + networkObject.Observers.Add(clientId); + } + + // Sanity check to make sure the owner is always included + // Itentionally checking as opposed to just assigning in order to generate notification. + if (!networkObject.Observers.Contains(ownerClientId)) + { + Debug.LogError($"Client-{ownerClientId} is the owner of {networkObject.name} but is not an observer! Adding owner, but there is a bug in observer synchronization!"); + networkObject.Observers.Add(ownerClientId); + } + } + } +#endif SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene); } - // Ran on both server and client + /// + /// This is only invoked to instantiate a serialized NetworkObject via + /// + /// internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene) { if (networkObject == null) @@ -639,7 +1048,7 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkO if (networkObject.IsSpawned) { - throw new SpawnStateException("Object is already spawned"); + throw new SpawnStateException($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!"); } SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene); @@ -649,7 +1058,7 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong { if (SpawnedObjects.ContainsKey(networkId)) { - Debug.LogWarning($"Trying to spawn {nameof(NetworkObject.NetworkObjectId)} {networkId} that already exists!"); + Debug.LogWarning($"[{NetworkManager.name}] Trying to spawn {networkObject.name} with a {nameof(NetworkObject.NetworkObjectId)} of {networkId} but it is already in the spawned list!"); return; } @@ -674,13 +1083,24 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkObject.DestroyWithScene = sceneObject || destroyWithScene; - networkObject.OwnerClientId = ownerClientId; - networkObject.IsPlayerObject = playerObject; + networkObject.OwnerClientId = ownerClientId; +#if NGO_DAMODE + // When spawned, previous owner is always the first assigned owner + networkObject.PreviousOwnerId = ownerClientId; + + // If this the player and the client is the owner, then lock ownership by default + if (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClientId == ownerClientId && playerObject) + { + networkObject.SetOwnershipLock(); + } +#endif SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject); SpawnedObjectsList.Add(networkObject); +#if !NGO_DAMODE + // TODO: This whole segement can be deleted when we remove NGO_DAMODE if (NetworkManager.IsServer) { if (playerObject) @@ -706,17 +1126,26 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong NetworkManager.LocalClient.PlayerObject = networkObject; } } +#endif +#if NGO_DAMODE + // If we are not running in DA mode, this is the server, and the NetworkObject has SpawnWithObservers set, + // then add all connected clients as observers + if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkObject.SpawnWithObservers) +#else // If we are the server and should spawn with observers if (NetworkManager.IsServer && networkObject.SpawnWithObservers) +#endif { // Add client observers - for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) + for (int i = 0; i < NetworkManager.ConnectedClientsIds.Count; i++) { - if (networkObject.CheckObjectVisibility == null || networkObject.CheckObjectVisibility(NetworkManager.ConnectedClientsList[i].ClientId)) + // If CheckObjectVisibility has a callback, then allow that method determine who the observers are. + if (networkObject.CheckObjectVisibility != null && !networkObject.CheckObjectVisibility(NetworkManager.ConnectedClientsIds[i])) { - networkObject.Observers.Add(NetworkManager.ConnectedClientsList[i].ClientId); + continue; } + networkObject.Observers.Add(NetworkManager.ConnectedClientsIds[i]); } } @@ -744,6 +1173,12 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong { networkObject.SubscribeToActiveSceneForSynch(); } +#if NGO_DAMODE + if (networkObject.IsPlayerObject) + { + UpdateNetworkClientPlayer(networkObject); + } +#endif // If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set // then assign this to the PrefabGlobalObjectIdHash @@ -756,19 +1191,62 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject) { // If we are a host and sending to the host's client id, then we can skip sending ourselves the spawn message. +#if NGO_DAMODE + var updateObservers = NetworkManager.DistributedAuthorityMode && networkObject.SpawnWithObservers; + + // Only skip if distributed authority mode is not enabled + if (clientId == NetworkManager.ServerClientId && !NetworkManager.DistributedAuthorityMode) +#else if (clientId == NetworkManager.ServerClientId) +#endif { return; } var message = new CreateObjectMessage { - ObjectInfo = networkObject.GetMessageSceneObject(clientId) + ObjectInfo = networkObject.GetMessageSceneObject(clientId), +#if NGO_DAMODE + IncludesSerializedObject = true, + UpdateObservers = NetworkManager.DistributedAuthorityMode, + ObserverIds = NetworkManager.DistributedAuthorityMode ? networkObject.Observers.ToArray() : null, +#endif }; var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId); NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); } +#if NGO_DAMODE + + /// + /// Only used to update object visibility/observers. + /// ** Clients are the only instances that use this method ** + /// + internal void SendSpawnCallForObserverUpdate(ulong[] newObservers, NetworkObject networkObject) + { + if (!NetworkManager.DistributedAuthorityMode) + { + throw new Exception("[SendSpawnCallForObserverUpdate] Invoking a distributed authority only method when distributed authority is not enabled!"); + } + + var message = new CreateObjectMessage + { + ObjectInfo = networkObject.GetMessageSceneObject(), + ObserverIds = networkObject.Observers.ToArray(), + NewObserverIds = newObservers.ToArray(), + IncludesSerializedObject = true, + UpdateObservers = true, + UpdateNewObservers = true, + }; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId); + foreach (var clientId in newObservers) + { + // TODO: We might want to track observer update sent as well? + NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); + } + } +#endif + internal ulong? GetSpawnParentId(NetworkObject networkObject) { NetworkObject parentNetworkObject = null; @@ -786,19 +1264,41 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject return parentNetworkObject.NetworkObjectId; } +#if NGO_DAMODE + internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false, bool playerDisconnect = false) +#else internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false) +#endif { if (!networkObject.IsSpawned) { - throw new SpawnStateException("Object is not spawned"); + NetworkLog.LogErrorServer("Object is not spawned!"); + return; } +#if NGO_DAMODE + if (!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) +#else if (!NetworkManager.IsServer) +#endif { - throw new NotServerException("Only server can despawn objects"); + NetworkLog.LogErrorServer("Only server can despawn objects"); + return; } +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode && networkObject.OwnerClientId != NetworkManager.LocalClientId) + { + if (!NetworkManager.DAHost || NetworkManager.DAHost && !playerDisconnect) + { + 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); +#else OnDespawnObject(networkObject, destroyObject); +#endif } // Makes scene objects ready to be reused @@ -913,11 +1413,15 @@ internal void DestroySceneObjects() { if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i])) { - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]); if (SpawnedObjects.ContainsKey(networkObjects[i].NetworkObjectId)) { + // This method invokes HandleNetworkPrefabDestroy, we only want to handle this once. OnDespawnObject(networkObjects[i], false); } + else // If not spawned, then just invoke the handler + { + NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]); + } } else { @@ -935,27 +1439,33 @@ internal void ServerSpawnSceneObjectsOnStartSweep() #else var networkObjects = UnityEngine.Object.FindObjectsOfType(); #endif - var networkObjectsToSpawn = new List(); - for (int i = 0; i < networkObjects.Length; i++) { if (networkObjects[i].NetworkManager == NetworkManager) { + // This used to be two loops. + // The first added all NetworkObjects to a list and the second spawned all NetworkObjects in the list. + // Now, a parent will set its children's IsSceneObject value when spawned, so we check for null or for true. if (networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject.HasValue && networkObjects[i].IsSceneObject.Value)) { - networkObjectsToSpawn.Add(networkObjects[i]); + var ownerId = networkObjects[i].OwnerClientId; +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + ownerId = NetworkManager.LocalClientId; + } +#endif + SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true); } } } - - - foreach (var networkObject in networkObjectsToSpawn) - { - SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, networkObject.OwnerClientId, true); - } } +#if NGO_DAMODE + internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false) +#else internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject) +#endif { if (NetworkManager == null) { @@ -972,42 +1482,130 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec // Removal of spawned object if (!SpawnedObjects.ContainsKey(networkObject.NetworkObjectId)) { - Debug.LogWarning($"Trying to destroy object {networkObject.NetworkObjectId} but it doesn't seem to exist anymore!"); + if (!NetworkManager.ShutdownInProgress) + { + Debug.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 +#if NGO_DAMODE + var distributedAuthority = NetworkManager.DistributedAuthorityMode; + if (!NetworkManager.ShutdownInProgress && (NetworkManager.IsServer || distributedAuthority)) +#else if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer) +#endif { + // Get all child NetworkObjects + var objectsToRemoveParent = networkObject.GetComponentsInChildren(); + // Move child NetworkObjects to the root when parent NetworkObject is destroyed - foreach (var spawnedNetObj in SpawnedObjectsList) + foreach (var spawnedNetObj in objectsToRemoveParent) { +#if NGO_DAMODE + if (spawnedNetObj == networkObject) + { + continue; + } +#endif var latestParent = spawnedNetObj.GetNetworkParenting(); - if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId) + // Only deparent the first generation children of the NetworkObject being spawned. + // Ignore any nested children under first generation children. + if (latestParent.HasValue && latestParent.Value != networkObject.NetworkObjectId) + { + continue; + } +#if NGO_DAMODE + // 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; +#endif + // Try to remove the parent using the cached WorldPositionStays value + // Note: WorldPositionStays will still default to true if this was an + // in-scene placed NetworkObject and parenting was predefined in the + // scene via the editor. + if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays()) { - // Try to remove the parent using the cached WorldPositioNStays value - // Note: WorldPositionStays will still default to true if this was an - // in-scene placed NetworkObject and parenting was predefined in the - // scene via the editor. - if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays()) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed"); - } - } - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); + NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed"); } } + else + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); + } } } networkObject.InvokeBehaviourNetworkDespawn(); +#if NGO_DAMODE + if (NetworkManager != null && ((NetworkManager.IsServer && (!distributedAuthority || + (distributedAuthority && modeDestroy))) || + (distributedAuthority && networkObject.OwnerClientId == NetworkManager.LocalClientId))) + { + if (NetworkManager.NetworkConfig.RecycleNetworkIds) + { + ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId() + { + NetworkId = networkObject.NetworkObjectId, + ReleaseTime = NetworkManager.RealTimeProvider.UnscaledTime + }); + } + 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)))) + { + // 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) + { + continue; + } + if (networkObject.IsNetworkVisibleTo(clientId)) + { + m_TargetClientIds.Add(clientId); + } + } + } + else // DANGO-TODO: If we 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) + { + var message = new DestroyObjectMessage + { + NetworkObjectId = networkObject.NetworkObjectId, + DeferredDespawnTick = networkObject.DeferredDespawnTick, + DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true, + IsTargetedDestroy = false, + IsDistributedAuthority = distributedAuthority, + }; + foreach (var clientId in m_TargetClientIds) + { + var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, networkObject, size); + } + } + } +#else if (NetworkManager != null && NetworkManager.IsServer) { if (NetworkManager.NetworkConfig.RecycleNetworkIds) @@ -1018,7 +1616,6 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec ReleaseTime = NetworkManager.RealTimeProvider.UnscaledTime }); } - if (networkObject != null) { // As long as we have any remaining clients, then notify of the object being destroy. @@ -1049,6 +1646,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } } } +#endif networkObject.IsSpawned = false; @@ -1057,6 +1655,26 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec SpawnedObjectsList.Remove(networkObject); } +#if NGO_DAMODE + // DANGO-TODO: When we fix the issue with observers not being applied to NetworkObjects, + // (client connect/disconnect) we can remove this hacky way of doing this. + // Basically, when a player disconnects and/or is destroyed they are removed as an observer from all other client + // NetworkOject instances. + if (networkObject.IsPlayerObject && !networkObject.IsOwner && networkObject.OwnerClientId != NetworkManager.LocalClientId) + { + foreach (var netObject in SpawnedObjects) + { + if (netObject.Value.Observers.Contains(networkObject.OwnerClientId)) + { + netObject.Value.Observers.Remove(networkObject.OwnerClientId); + } + } + } + if (networkObject.IsPlayerObject) + { + RemovePlayerObject(networkObject); + } +#endif // Always clear out the observers list when despawned networkObject.Observers.Clear(); @@ -1075,7 +1693,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } /// - /// Updates all spawned for the specified newly connected client + /// Updates all spawned for the specified newly connected client /// Note: if the clientId is the server then it is observable to all spawned 's /// /// @@ -1089,8 +1707,8 @@ internal void UpdateObservedNetworkObjects(ulong clientId) // If the NetworkObject has no visibility check then prepare to add this client as an observer if (sobj.CheckObjectVisibility == null) { - // If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server - if (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId) + // If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server/host/DAHost + if (!sobj.Observers.Contains(clientId) && (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId)) { sobj.Observers.Add(clientId); } @@ -1115,6 +1733,21 @@ internal void UpdateObservedNetworkObjects(ulong clientId) /// internal void HandleNetworkObjectShow() { +#if NGO_DAMODE + // In distributed authority mode, we send a single message that is broadcasted to all clients + // that will be shown the object (i.e. 1 message to service that then broadcasts that to the + // targeted clients). When using a DAHost, we skip this and send like we do in client-server + if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) + { + foreach (var entry in ClientsToShowObject) + { + SendSpawnCallForObserverUpdate(entry.Value.ToArray(), entry.Key); + } + ClientsToShowObject.Clear(); + ObjectsToShowToClient.Clear(); + return; + } +#endif // Handle NetworkObjects to show foreach (var client in ObjectsToShowToClient) { @@ -1130,6 +1763,269 @@ internal void HandleNetworkObjectShow() internal NetworkSpawnManager(NetworkManager networkManager) { NetworkManager = networkManager; +#if !NGO_DAMODE } +#else + } + + /// + /// DANGO-TODO: Until we have the CMB Server end-to-end with all features verified working via integration tests, + /// I am keeping this debug toggle available. (NSS) + /// + internal bool EnableDistributeLogging = false; + + /// + /// Fills the first table passed in with the current distribution of prefab types relative to their owners + /// Fills the second table passed in with the total number of spawned objects of that particular type. + /// The second table allows us to calculate how many objects per client there should be in order to determine + /// how many of that type should be distributed. + /// + /// the table to populate + /// the total number of the specific object type to distribute + internal void GetObjectDistribution(ref Dictionary>> objectByTypeAndOwner, ref Dictionary objectTypeCount) + { + // DANGO-TODO-MVP: Remove this once the service handles object distribution + var onlyIncludeOwnedObjects = NetworkManager.CMBServiceConnection; + + foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) + { + if (networkObject.IsOwnershipDistributable && !networkObject.IsOwnershipLocked) + { + if (networkObject.transform.parent != null) + { + var parentNetworkObject = networkObject.transform.parent.GetComponent(); + if (parentNetworkObject != null && parentNetworkObject.OwnerClientId == networkObject.OwnerClientId) + { + continue; + } + } + + if (networkObject.IsSceneObject.Value) + { + continue; + } + + if (!objectTypeCount.ContainsKey(networkObject.GlobalObjectIdHash)) + { + objectTypeCount.Add(networkObject.GlobalObjectIdHash, 0); + } + objectTypeCount[networkObject.GlobalObjectIdHash] += 1; + + // DANGO-TODO-MVP: Remove this once the service handles object distribution + if (onlyIncludeOwnedObjects && !networkObject.IsOwner) + { + continue; + } + + // Divide up by prefab type (GlobalObjectIdHash) to get a better distribution of object types + if (!objectByTypeAndOwner.ContainsKey(networkObject.GlobalObjectIdHash)) + { + objectByTypeAndOwner.Add(networkObject.GlobalObjectIdHash, new Dictionary>()); + } + + // Sub-divide each type by owner + if (!objectByTypeAndOwner[networkObject.GlobalObjectIdHash].ContainsKey(networkObject.OwnerClientId)) + { + objectByTypeAndOwner[networkObject.GlobalObjectIdHash].Add(networkObject.OwnerClientId, new List()); + } + + // Add to the client's spawned object list + objectByTypeAndOwner[networkObject.GlobalObjectIdHash][networkObject.OwnerClientId].Add(networkObject); + } + } + } + + internal void DistributeNetworkObjects(ulong clientId) + { + // Distributed authority mode ownership distribution + // DANGO-TODO-MVP: Remove the session owner object distribution check once the service handles object distribution + if (NetworkManager.DistributedAuthorityMode && (NetworkManager.DAHost || NetworkManager.CMBServiceConnection)) + { + // DA-NGO CMB SERVICE NOTES: + // The most basic object distribution should be broken up into a table of spawned object types + // where each type contains a list of each client's owned objects of that type that can be + // distributed. + // The table format: + // [GlobalObjectIdHashValue][ClientId][List of Owned Objects] + var distributedNetworkObjects = new Dictionary>>(); + + // DA-NGO CMB SERVICE NOTES: + // This is optional, but I found it easier to get the total count of spawned objects for each prefab + // type contained in the previous table in order to be able to calculate the targeted object distribution + // count of that type per client. + var objectTypeCount = new Dictionary(); + + // Get all spawned objects by type and then by client owner that are spawned and can be distributed + GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); + + var clientCount = NetworkManager.ConnectedClientsIds.Count; + + // Cycle through each prefab type + foreach (var objectTypeEntry in distributedNetworkObjects) + { + // Calculate the number of objects that should be distributed amongst the clients + var totalObjectsToDistribute = objectTypeCount[objectTypeEntry.Key]; + var objPerClientF = totalObjectsToDistribute * (1.0f / clientCount); + var floorValue = (int)Math.Floor(objPerClientF); + var fractional = objPerClientF - floorValue; + var objPerClient = 0; + if (fractional >= 0.556f) + { + objPerClient = (int)Math.Round(totalObjectsToDistribute * (1.0f / clientCount)); + } + else + { + objPerClient = floorValue; + } + + // If the object per client count is zero, then move to the next type. + if (objPerClient <= 0) + { + continue; + } + + // Evenly distribute this object type amongst the clients + foreach (var ownerList in objectTypeEntry.Value) + { + if (ownerList.Value.Count <= 1) + { + continue; + } + + var maxDistributeCount = Mathf.Max(ownerList.Value.Count - objPerClient, 1); + var distributed = 0; + + // For now when we have more players then distributed NetworkObjects that + // a specific client owns, just assign half of the NetworkObjects to the new client + var offsetCount = Mathf.Max((int)Math.Round((float)(ownerList.Value.Count / objPerClient)), 1); + if (EnableDistributeLogging) + { + Debug.Log($"[{objPerClient} of {totalObjectsToDistribute}][Client-{ownerList.Key}] Count: {ownerList.Value.Count} | ObjPerClient: {objPerClient} | maxD: {maxDistributeCount} | Offset: {offsetCount}"); + } + + for (int i = 0; i < ownerList.Value.Count; i++) + { + if ((i % offsetCount) == 0) + { + ChangeOwnership(ownerList.Value[i], clientId, true); + if (EnableDistributeLogging) + { + Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}"); + } + distributed++; + } + if (distributed == maxDistributeCount) + { + break; + } + } + } + } + + // If EnableDistributeLogging is enabled, log the object type distribution counts per client + if (EnableDistributeLogging) + { + var builder = new StringBuilder(); + distributedNetworkObjects.Clear(); + objectTypeCount.Clear(); + GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); + builder.AppendLine($"Client Relative Distributed Object Count: (distribution follows)"); + // Cycle through each prefab type + foreach (var objectTypeEntry in distributedNetworkObjects) + { + builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}][Total Count: {objectTypeCount[objectTypeEntry.Key]}]"); + builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}] Distribution:"); + // Evenly distribute this type amongst clients + foreach (var ownerList in objectTypeEntry.Value) + { + builder.AppendLine($"[Client-{ownerList.Key}] Count: {ownerList.Value.Count}"); + } + } + Debug.Log(builder.ToString()); + } + } + } + + internal struct DeferredDespawnObject + { + public int TickToDespawn; + public bool HasDeferredDespawnCheck; + public ulong NetworkObjectId; + } + + internal List DeferredDespawnObjects = new List(); + + /// + /// Adds a deferred despawn entry to be processed + /// + /// 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) + { + var deferredDespawnObject = new DeferredDespawnObject() + { + TickToDespawn = tickToDespawn, + HasDeferredDespawnCheck = hasDeferredDespawnCheck, + NetworkObjectId = networkObjectId, + }; + DeferredDespawnObjects.Add(deferredDespawnObject); + } + + /// + /// Processes any deferred despawn entries + /// + internal void DeferredDespawnUpdate(NetworkTime serverTime) + { + // Exit early if there is nothing to process + if (DeferredDespawnObjects.Count == 0) + { + return; + } + var currentTick = serverTime.Tick; + var deferredCallbackObjects = DeferredDespawnObjects.Where((c) => c.HasDeferredDespawnCheck); + var deferredCallbackCount = deferredCallbackObjects.Count(); + for (int i = 0; i < deferredCallbackCount - 1; i++) + { + var deferredObjectEntry = deferredCallbackObjects.ElementAt(i); + var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId]; + // Double check to make sure user did not remove the callback + if (networkObject.OnDeferredDespawnComplete != null) + { + // If the user callback returns true, then we despawn it this tick + if (networkObject.OnDeferredDespawnComplete.Invoke()) + { + deferredObjectEntry.TickToDespawn = currentTick; + } + else + { + // If the user callback does not verify the NetworkObject can be despawned, + // continue setting this value in the event user script adjusts it. + deferredObjectEntry.TickToDespawn = networkObject.DeferredDespawnTick; + } + } + else + { + // If it was removed, then in case it is being left to defer naturally exclude it + // from the next query. + deferredObjectEntry.HasDeferredDespawnCheck = false; + } + } + + var despawnObjects = DeferredDespawnObjects.Where((c) => c.TickToDespawn < currentTick).ToList(); + foreach (var deferredObjectEntry in despawnObjects) + { + if (!SpawnedObjects.ContainsKey(deferredObjectEntry.NetworkObjectId)) + { + DeferredDespawnObjects.Remove(deferredObjectEntry); + continue; + } + var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId]; + // Local instance despawns the instance + OnDespawnObject(networkObject, true); + DeferredDespawnObjects.Remove(deferredObjectEntry); + } + } +#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs b/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs index 07740c8702..2b1bc64501 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs @@ -6,5 +6,8 @@ internal interface IRealTimeProvider float UnscaledTime { get; } float UnscaledDeltaTime { get; } float DeltaTime { get; } +#if NGO_DAMODE + float FixedDeltaTime { get; } +#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 7af210b9bf..71e3349e36 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -113,9 +113,9 @@ public NetworkTime ToFixedTime() /// /// The number of ticks ago we're querying the time /// - public NetworkTime TimeTicksAgo(int ticks) + public NetworkTime TimeTicksAgo(int ticks, float offset = 0.0f) { - return this - new NetworkTime(TickRate, ticks); + return this - new NetworkTime(TickRate, ticks, offset); } private void UpdateCache() diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs b/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs index 840180d580..fa56d3fb8c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs @@ -8,5 +8,8 @@ internal class RealTimeProvider : IRealTimeProvider public float UnscaledTime => Time.unscaledTime; public float UnscaledDeltaTime => Time.unscaledDeltaTime; public float DeltaTime => Time.deltaTime; +#if NGO_DAMODE + public float FixedDeltaTime => Time.fixedDeltaTime; +#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs index b1bd3fa98f..9e71f36459 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs @@ -106,7 +106,11 @@ private void AppendDataAtTail(ArraySegment data) { unsafe { +#if UTP_TRANSPORT_2_0_ABOVE + var writer = new DataStreamWriter(m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex); +#else var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex); +#endif writer.WriteInt(data.Count); @@ -145,7 +149,11 @@ public bool PushMessage(ArraySegment message) { unsafe { +#if UTP_TRANSPORT_2_0_ABOVE + UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), m_Data.GetUnsafePtr() + HeadIndex, Length); +#else UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length); +#endif } TailIndex = Length; @@ -231,7 +239,11 @@ public int FillWriterWithMessages(ref DataStreamWriter writer, int softMaxBytes if (bytesToWrite > softMaxBytes && bytesToWrite <= writer.Capacity) { writer.WriteInt(messageLength); +#if UTP_TRANSPORT_2_0_ABOVE + WriteBytes(ref writer, m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); +#else WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); +#endif return bytesToWrite; } @@ -248,7 +260,11 @@ public int FillWriterWithMessages(ref DataStreamWriter writer, int softMaxBytes if (bytesWritten + bytesToWrite <= softMaxBytes) { writer.WriteInt(messageLength); +#if UTP_TRANSPORT_2_0_ABOVE + WriteBytes(ref writer, m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); +#else WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); +#endif readerOffset += bytesToWrite; bytesWritten += bytesToWrite; @@ -292,7 +308,11 @@ public int FillWriterWithBytes(ref DataStreamWriter writer, int maxBytes = 0) unsafe { +#if UTP_TRANSPORT_2_0_ABOVE + WriteBytes(ref writer, m_Data.GetUnsafePtr() + HeadIndex, copyLength); +#else WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); +#endif } return copyLength; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index 05998d7136..d7f3dfe5f5 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -23,6 +23,10 @@ internal struct SceneEntry public Scene Scene; } +#if NGO_DAMODE + public bool IsIntegrationTest() { return true; } +#endif + internal static Dictionary>> SceneNameToSceneHandles = new Dictionary>>(); // All IntegrationTestSceneHandler instances register their associated NetworkManager diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs index 462fd74f76..02568e4e34 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs @@ -1,12 +1,24 @@ +using System; using System.Runtime.CompilerServices; using UnityEngine; +using Random = UnityEngine.Random; namespace Unity.Netcode.TestHelpers.Runtime { public abstract class IntegrationTestWithApproximation : NetcodeIntegrationTest { - private const float k_AproximateDeltaVariance = 0.01f; + private const float k_AproximateDeltaVariance = 0.016f; + + protected string GetVector3Values(ref Vector3 vector3) + { + return $"({vector3.x:F6},{vector3.y:F6},{vector3.z:F6})"; + } + + protected string GetVector3Values(Vector3 vector3) + { + return GetVector3Values(ref vector3); + } protected virtual float GetDeltaVarianceThreshold() { @@ -40,17 +52,17 @@ protected bool Approximately(float a, float b) protected bool Approximately(Vector2 a, Vector2 b) { var deltaVariance = GetDeltaVarianceThreshold(); - return Mathf.Abs(a.x - b.x) <= deltaVariance && - Mathf.Abs(a.y - b.y) <= deltaVariance; + return Math.Round(Mathf.Abs(a.x - b.x), 2) <= deltaVariance && + Math.Round(Mathf.Abs(a.y - b.y), 2) <= deltaVariance; } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected bool Approximately(Vector3 a, Vector3 b) { var deltaVariance = GetDeltaVarianceThreshold(); - return Mathf.Abs(a.x - b.x) <= deltaVariance && - Mathf.Abs(a.y - b.y) <= deltaVariance && - Mathf.Abs(a.z - b.z) <= deltaVariance; + return Math.Round(Mathf.Abs(a.x - b.x), 2) <= deltaVariance && + Math.Round(Mathf.Abs(a.y - b.y), 2) <= deltaVariance && + Math.Round(Mathf.Abs(a.z - b.z), 2) <= deltaVariance; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -75,5 +87,12 @@ protected Vector3 GetRandomVector3(float min, float max) return new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(min, max)); } +#if NGO_DAMODE + public IntegrationTestWithApproximation(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + + public IntegrationTestWithApproximation(HostOrServer hostOrServer) : base(hostOrServer) { } + + public IntegrationTestWithApproximation() : base() { } } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs index f97b5dca26..9fc51a0d6a 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs @@ -7,6 +7,11 @@ public class MockTimeProvider : IRealTimeProvider public float UnscaledDeltaTime => (float)s_DoubleDelta; public float DeltaTime => (float)s_DoubleDelta; +#if NGO_DAMODE + //DANGO-TODO: Figure out how we want to handle time travel with fixed delta time. + public float FixedDeltaTime => (float)s_DoubleDelta; +#endif + public static float StaticRealTimeSinceStartup => (float)s_DoubleRealTime; public static float StaticUnscaledTime => (float)s_DoubleRealTime; public static float StaticUnscaledDeltaTime => (float)s_DoubleDelta; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 1c8b4c6eab..611fcce8a2 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Text; using NUnit.Framework; using Unity.Netcode.RuntimeTests; using Unity.Netcode.Transports.UTP; @@ -29,6 +30,13 @@ public abstract class NetcodeIntegrationTest protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate); public NetcodeLogAssert NetcodeLogAssert; + public enum SceneManagementState + { + SceneManagementEnabled, + SceneManagementDisabled + } + + private StringBuilder m_InternalErrorLog = new StringBuilder(); /// /// Registered list of all NetworkObjects spawned. @@ -112,7 +120,10 @@ public enum NetworkManagerInstatiationMode public enum HostOrServer { Host, - Server + Server, +#if NGO_DAMODE + DAHost +#endif } protected GameObject m_PlayerPrefab; @@ -129,6 +140,28 @@ public enum HostOrServer protected Dictionary> m_PlayerNetworkObjects = new Dictionary>(); protected bool m_UseHost = true; +#if NGO_DAMODE + protected bool m_DistributedAuthority; + protected SessionModeTypes m_SessionModeType = SessionModeTypes.ClientServer; + + protected virtual bool UseCMBService() + { + return false; + } + + protected virtual SessionModeTypes OnGetSessionmode() + { + return m_SessionModeType; + } + + protected void SetDistributedAuthorityProperties(NetworkManager networkManager) + { + networkManager.NetworkConfig.SessionMode = m_SessionModeType; + networkManager.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_DistributedAuthority; + networkManager.NetworkConfig.UseCMBService = UseCMBService() && m_DistributedAuthority; + } + +#endif protected int m_TargetFrameRate = 60; private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode; @@ -338,6 +371,7 @@ public IEnumerator SetUp() else { yield return StartServerAndClients(); + } } @@ -367,6 +401,7 @@ private void CreatePlayerPrefab() m_PlayerPrefab = new GameObject("Player"); OnPlayerPrefabGameObjectCreated(); NetworkObject networkObject = m_PlayerPrefab.AddComponent(); + networkObject.IsSceneObject = false; // Make it a prefab NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); @@ -455,6 +490,9 @@ protected IEnumerator CreateAndStartNewClient() { var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel); networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; +#if NGO_DAMODE + SetDistributedAuthorityProperties(networkManager); +#endif // Notification that the new client (NetworkManager) has been created // in the event any modifications need to be made before starting the client @@ -483,12 +521,68 @@ protected IEnumerator CreateAndStartNewClient() Object.DestroyImmediate(networkManager.gameObject); } - AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!"); + AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!\n {m_InternalErrorLog}"); ClientNetworkManagerPostStart(networkManager); +#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) + { + yield return WaitForConditionOrTimeOut(() => AllPlayerObjectClonesSpawned(networkManager)); + AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{networkManager.LocalClientId}'s player object!"); + } +#endif + VerboseDebug($"[{networkManager.name}] Created and connected!"); } } +#if NGO_DAMODE + private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient) + { + m_InternalErrorLog.Clear(); + // Continue to populate the PlayerObjects list until all player object (local and clone) are found + ClientNetworkManagerPostStart(joinedClient); + + var playerObjectRelative = m_ServerNetworkManager.SpawnManager.PlayerObjects.Where((c) => c.OwnerClientId == joinedClient.LocalClientId).FirstOrDefault(); + if (playerObjectRelative == null) + { + m_InternalErrorLog.Append($"[AllPlayerObjectClonesSpawned][Server-Side] Joining Client-{joinedClient.LocalClientId} was not populated in the {nameof(NetworkSpawnManager.PlayerObjects)} list!"); + return false; + } + else + { + // Go ahead and create an entry for this new client + if (!m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(joinedClient.LocalClientId)) + { + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].Add(joinedClient.LocalClientId, playerObjectRelative); + } + } + + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (clientNetworkManager.LocalClientId == joinedClient.LocalClientId) + { + continue; + } + + playerObjectRelative = clientNetworkManager.SpawnManager.PlayerObjects.Where((c) => c.OwnerClientId == joinedClient.LocalClientId).FirstOrDefault(); + if (playerObjectRelative == null) + { + m_InternalErrorLog.Append($"[AllPlayerObjectClonesSpawned][Client-{clientNetworkManager.LocalClientId}] Client-{joinedClient.LocalClientId} was not populated in the {nameof(NetworkSpawnManager.PlayerObjects)} list!"); + return false; + } + else + { + // Go ahead and create an entry for this new client + if (!m_PlayerNetworkObjects[clientNetworkManager.LocalClientId].ContainsKey(joinedClient.LocalClientId)) + { + m_PlayerNetworkObjects[clientNetworkManager.LocalClientId].Add(joinedClient.LocalClientId, playerObjectRelative); + } + } + } + return true; + } +#endif + /// /// This will create, start, and connect a new client while in the middle of an /// integration test. @@ -497,6 +591,9 @@ protected void CreateAndStartNewClientWithTimeTravel() { var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel); networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; +#if NGO_DAMODE + SetDistributedAuthorityProperties(networkManager); +#endif // Notification that the new client (NetworkManager) has been created // in the event any modifications need to be made before starting the client @@ -584,9 +681,16 @@ protected void CreateServerAndClients(int numberOfClients) // Set the player prefab for the server and clients m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; +#if NGO_DAMODE + SetDistributedAuthorityProperties(m_ServerNetworkManager); +#endif + foreach (var client in m_ClientNetworkManagers) { client.NetworkConfig.PlayerPrefab = m_PlayerPrefab; +#if NGO_DAMODE + SetDistributedAuthorityProperties(client); +#endif } // Provides opportunity to allow child derived classes to @@ -733,22 +837,33 @@ protected IEnumerator StartServerAndClients() // Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server // is started and after each client is started. + +#if NGO_DAMODE + // When using the CMBService, don't start the server. + bool startServer = !(UseCMBService() && m_DistributedAuthority); + if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, startServer, m_ServerNetworkManager, m_ClientNetworkManagers)) +#else if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers)) +#endif { Debug.LogError("Failed to start instances"); Assert.Fail("Failed to start instances"); } // When scene management is enabled, we need to re-apply the scenes populated list since we have overriden the ISceneManagerHandler - // imeplementation at this point. This assures any pre-loaded scenes will be automatically assigned to the server and force clients + // imeplementation at this point. This assures any pre-loaded scenes will be automatically assigned to the server and force clients // to load their own scenes. if (m_ServerNetworkManager.NetworkConfig.EnableSceneManagement) { - var scenesLoaded = m_ServerNetworkManager.SceneManager.ScenesLoaded; - m_ServerNetworkManager.SceneManager.SceneManagerHandler.PopulateLoadedScenes(ref scenesLoaded, m_ServerNetworkManager); +#if NGO_DAMODE + if (startServer) +#endif + { + var scenesLoaded = m_ServerNetworkManager.SceneManager.ScenesLoaded; + m_ServerNetworkManager.SceneManager.SceneManagerHandler.PopulateLoadedScenes(ref scenesLoaded, m_ServerNetworkManager); + } } - if (LogAllMessages) { EnableMessageLogging(); @@ -765,7 +880,7 @@ protected IEnumerator StartServerAndClients() // Wait for all clients to connect yield return WaitForClientsConnectedOrTimeOut(); - AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!"); + AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!\n {m_InternalErrorLog}"); if (m_UseHost || m_ServerNetworkManager.IsHost) { @@ -786,9 +901,27 @@ protected IEnumerator StartServerAndClients() m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject); } } - +#if NGO_DAMODE + if (m_DistributedAuthority) + { + //yield return WaitForConditionOrTimeOut(AllClientPlayersSpawned); + //AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn all player objects!"); + foreach (var networkManager in m_ClientNetworkManagers) + { + if (networkManager.DistributedAuthorityMode) + { + yield return WaitForConditionOrTimeOut(() => AllPlayerObjectClonesSpawned(networkManager)); + AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{networkManager.LocalClientId}'s player object!\n {m_InternalErrorLog}"); + } + } + if (m_ServerNetworkManager != null) + { + yield return WaitForConditionOrTimeOut(() => AllPlayerObjectClonesSpawned(m_ServerNetworkManager)); + AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{m_ServerNetworkManager.LocalClientId}'s player object!\n {m_InternalErrorLog}"); + } + } +#endif ClientNetworkManagerPostStartInit(); - // Notification that at this time the server and client(s) are instantiated, // started, and connected on both sides. yield return OnServerAndClientsConnected(); @@ -810,7 +943,13 @@ protected void StartServerAndClientsWithTimeTravel() // Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server // is started and after each client is started. +#if NGO_DAMODE + // When using the CMBService, don't start the server. + var usingCMBService = UseCMBService() && m_DistributedAuthority; + if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, !usingCMBService, m_ServerNetworkManager, m_ClientNetworkManagers)) +#else if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers)) +#endif { Debug.LogError("Failed to start instances"); Assert.Fail("Failed to start instances"); @@ -860,6 +999,26 @@ protected void StartServerAndClientsWithTimeTravel() } } +#if NGO_DAMODE + if (m_DistributedAuthority) + { + + foreach (var networkManager in m_ClientNetworkManagers) + { + if (networkManager.DistributedAuthorityMode) + { + WaitForConditionOrTimeOutWithTimeTravel(() => AllPlayerObjectClonesSpawned(m_ServerNetworkManager)); + AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{networkManager.LocalClientId}'s player object!"); + } + } + if (m_ServerNetworkManager != null) + { + WaitForConditionOrTimeOutWithTimeTravel(() => AllPlayerObjectClonesSpawned(m_ServerNetworkManager)); + AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{m_ServerNetworkManager.LocalClientId}'s player object!"); + } + } +#endif + ClientNetworkManagerPostStartInit(); // Notification that at this time the server and client(s) are instantiated, @@ -968,6 +1127,45 @@ protected void ShutdownAndCleanUp() VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}"); } + protected IEnumerator CoroutineShutdownAndCleanUp() + { + VerboseDebug($"Entering {nameof(ShutdownAndCleanUp)}"); + // Shutdown and clean up both of our NetworkManager instances + try + { + DeRegisterSceneManagerHandler(); + + NetcodeIntegrationTestHelpers.Destroy(); + + m_PlayerNetworkObjects.Clear(); + s_GlobalNetworkObjects.Clear(); + } + catch (Exception e) + { + throw e; + } + finally + { + if (m_PlayerPrefab != null) + { + Object.DestroyImmediate(m_PlayerPrefab); + m_PlayerPrefab = null; + } + } + + // Allow time for NetworkManagers to fully shutdown + yield return s_DefaultWaitForTick; + + // Cleanup any remaining NetworkObjects + DestroySceneNetworkObjects(); + + UnloadRemainingScenes(); + + // reset the m_ServerWaitForTick for the next test to initialize + s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate); + VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}"); + } + /// /// Note: For mode /// this is called before ShutdownAndCleanUp. @@ -997,7 +1195,14 @@ public IEnumerator TearDown() if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest) { - ShutdownAndCleanUp(); + if (m_TearDownIsACoroutine) + { + yield return CoroutineShutdownAndCleanUp(); + } + else + { + ShutdownAndCleanUp(); + } } if (m_EnableTimeTravel) @@ -1233,11 +1438,35 @@ public bool WaitForConditionOrTimeOutWithTimeTravel(IConditionalPredicate condit /// An array of clients to be checked protected IEnumerator WaitForClientsConnectedOrTimeOut(NetworkManager[] clientsToCheck) { - var remoteClientCount = clientsToCheck.Length; - var serverClientCount = m_ServerNetworkManager.IsHost ? remoteClientCount + 1 : remoteClientCount; + yield return WaitForConditionOrTimeOut(() => CheckClientsConnected(clientsToCheck)); + } + + /// + /// Validation for clients connected that includes additional information for easier troubleshooting purposes. + /// + private bool CheckClientsConnected(NetworkManager[] clientsToCheck) + { + m_InternalErrorLog.Clear(); + var allClientsConnected = true; - yield return WaitForConditionOrTimeOut(() => clientsToCheck.Where((c) => c.IsConnectedClient).Count() == remoteClientCount && - m_ServerNetworkManager.ConnectedClients.Count == serverClientCount); + for (int i = 0; i < clientsToCheck.Length; i++) + { + if (!clientsToCheck[i].IsConnectedClient) + { + allClientsConnected = false; + m_InternalErrorLog.AppendLine($"[Client-{i + 1}] Client is not connected!"); + } + } + var expectedCount = m_ServerNetworkManager.IsHost ? clientsToCheck.Length + 1 : clientsToCheck.Length; + var currentCount = m_ServerNetworkManager.ConnectedClients.Count; + + if (currentCount != expectedCount) + { + allClientsConnected = false; + m_InternalErrorLog.AppendLine($"[Server-Side] Expected {expectedCount} clients to connect but only {currentCount} connected!"); + } + + return allClientsConnected; } /// @@ -1289,7 +1518,7 @@ internal IEnumerator WaitForMessageReceived(List wiatForRecei // Used to determine if all clients received the CreateObjectMessage var hooks = new MessageHooksConditional(messageHookEntriesForSpawn); yield return WaitForConditionOrTimeOut(hooks); - Assert.False(s_GlobalTimeoutHelper.TimedOut); + AssertOnTimeout($"Timed out waiting for message type {typeof(T).Name}!"); } internal IEnumerator WaitForMessagesReceived(List messagesInOrder, List waitForReceivedBy, ReceiptType type = ReceiptType.Handled) @@ -1309,7 +1538,12 @@ internal IEnumerator WaitForMessagesReceived(List messagesInOrder, List(List waitF // Used to determine if all clients received the CreateObjectMessage var hooks = new MessageHooksConditional(messageHookEntriesForSpawn); - Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks)); + Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks), $"[Message Not Recieved] {hooks.GetHooksStillWaiting()}"); } internal void WaitForMessagesReceivedWithTimeTravel(List messagesInOrder, List waitForReceivedBy, ReceiptType type = ReceiptType.Handled) @@ -1345,7 +1579,7 @@ internal void WaitForMessagesReceivedWithTimeTravel(List messagesInOrder, // Used to determine if all clients received the CreateObjectMessage var hooks = new MessageHooksConditional(messageHookEntriesForSpawn); - Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks)); + Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks), $"[Messages Not Recieved] {hooks.GetHooksStillWaiting()}"); } /// @@ -1361,8 +1595,13 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) $"but before {nameof(OnStartedServerAndClients)}!"; Assert.IsNotNull(m_ServerNetworkManager, prefabCreateAssertError); Assert.IsFalse(m_ServerNetworkManager.IsListening, prefabCreateAssertError); - - return NetcodeIntegrationTestHelpers.CreateNetworkObjectPrefab(baseName, m_ServerNetworkManager, m_ClientNetworkManagers); + var prefabObject = NetcodeIntegrationTestHelpers.CreateNetworkObjectPrefab(baseName, m_ServerNetworkManager, m_ClientNetworkManagers); +#if NGO_DAMODE + // DANGO-TODO: Ownership flags could require us to change this + // For testing purposes, we default to true for the distribute ownership property when in distirbuted authority session mode. + prefabObject.GetComponent().Ownership |= NetworkObject.OwnershipStatus.Distributable; +#endif + return prefabObject; } /// @@ -1375,6 +1614,16 @@ protected GameObject SpawnObject(GameObject prefabGameObject, NetworkManager own return SpawnObject(prefabNetworkObject, owner, destroyWithScene); } + /// + /// Overloaded method + /// + protected GameObject SpawnPlayerObject(GameObject prefabGameObject, NetworkManager owner, bool destroyWithScene = false) + { + var prefabNetworkObject = prefabGameObject.GetComponent(); + Assert.IsNotNull(prefabNetworkObject, $"{nameof(GameObject)} {prefabGameObject.name} does not have a {nameof(NetworkObject)} component!"); + return SpawnObject(prefabNetworkObject, owner, destroyWithScene, true); + } + /// /// Spawn a NetworkObject prefab instance /// @@ -1382,11 +1631,59 @@ protected GameObject SpawnObject(GameObject prefabGameObject, NetworkManager own /// the owner of the instance /// default is false /// GameObject instance spawned - private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager owner, bool destroyWithScene = false) + private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager owner, bool destroyWithScene = false, bool isPlayerObject = false) { Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash > 0, $"{nameof(GameObject)} {prefabNetworkObject.name} has a {nameof(NetworkObject.GlobalObjectIdHash)} value of 0! Make sure to make it a valid prefab before trying to spawn!"); var newInstance = Object.Instantiate(prefabNetworkObject.gameObject); var networkObjectToSpawn = newInstance.GetComponent(); + +#if NGO_DAMODE + if (owner.NetworkConfig.SessionMode == SessionModeTypes.DistributedAuthority) + { + networkObjectToSpawn.NetworkManagerOwner = owner; // Required to assure the client does the spawning + if (isPlayerObject) + { + networkObjectToSpawn.SpawnAsPlayerObject(owner.LocalClientId, destroyWithScene); + } + else + { + networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); + } + } + else + { + networkObjectToSpawn.NetworkManagerOwner = m_ServerNetworkManager; // Required to assure the server does the spawning + if (owner == m_ServerNetworkManager) + { + if (m_UseHost) + { + if (isPlayerObject) + { + networkObjectToSpawn.SpawnAsPlayerObject(owner.LocalClientId, destroyWithScene); + } + else + { + networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); + } + } + else + { + networkObjectToSpawn.Spawn(destroyWithScene); + } + } + else + { + if (isPlayerObject) + { + networkObjectToSpawn.SpawnAsPlayerObject(owner.LocalClientId, destroyWithScene); + } + else + { + networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); + } + } + } +#else networkObjectToSpawn.NetworkManagerOwner = m_ServerNetworkManager; // Required to assure the server does the spawning if (owner == m_ServerNetworkManager) { @@ -1403,6 +1700,8 @@ private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager { networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); } +#endif + return newInstance; } @@ -1441,7 +1740,21 @@ private List SpawnObjects(NetworkObject prefabNetworkObject, Network /// public NetcodeIntegrationTest() { +#if NGO_DAMODE + m_SessionModeType = OnGetSessionmode(); + m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; + NetworkMessageManager.EnableMessageOrderConsoleLog = false; +#endif + + } + +#if NGO_DAMODE + public NetcodeIntegrationTest(SessionModeTypes sessionMode) + { + m_SessionModeType = sessionMode; + m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; } +#endif /// /// Optional Host or Server integration tests @@ -1461,7 +1774,13 @@ public NetcodeIntegrationTest() /// public NetcodeIntegrationTest(HostOrServer hostOrServer) { - m_UseHost = hostOrServer == HostOrServer.Host ? true : false; +#if NGO_DAMODE + m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost; + m_SessionModeType = hostOrServer == HostOrServer.DAHost ? SessionModeTypes.DistributedAuthority : SessionModeTypes.ClientServer; + m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; +#else + m_UseHost = hostOrServer == HostOrServer.Host; +#endif } /// @@ -1492,7 +1811,7 @@ private void UnloadRemainingScenes() } } - private System.Text.StringBuilder m_WaitForLog = new System.Text.StringBuilder(); + private StringBuilder m_WaitForLog = new StringBuilder(); private void LogWaitForMessages() { diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index bf26f9f05a..4c9cfbd695 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -410,12 +410,33 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan { networkManager.SceneManager.ScenesLoaded.Add(scene.handle, scene); } +#if NGO_DAMODE + // In distributed authority we need to check if this scene is already added + if (networkManager.DistributedAuthorityMode) + { + if (!networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(scene.handle)) + { + networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); + } + + if (!networkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(scene.handle)) + { + networkManager.SceneManager.ClientSceneHandleToServerSceneHandle.Add(scene.handle, scene.handle); + } + return; + } +#endif networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); } } public delegate void BeforeClientStartCallback(); + internal static bool Start(bool host, bool startServer, NetworkManager server, NetworkManager[] clients) + { + return Start(host, server, clients, null, startServer); + } + /// /// Starts NetworkManager instances created by the Create method. /// @@ -424,7 +445,7 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan /// The Clients NetworkManager /// called immediately after server is started and before client(s) are started /// - public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, BeforeClientStartCallback callback = null) + public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, BeforeClientStartCallback callback = null, bool startServer = true) { if (s_IsStarted) { @@ -433,24 +454,26 @@ public static bool Start(bool host, NetworkManager server, NetworkManager[] clie s_IsStarted = true; s_ClientCount = clients.Length; - - if (host) + var hooks = (MultiInstanceHooks)null; + if (startServer) { - server.StartHost(); - } - else - { - server.StartServer(); - } - - var hooks = new MultiInstanceHooks(); - server.ConnectionManager.MessageManager.Hook(hooks); - s_Hooks[server] = hooks; + if (host) + { + server.StartHost(); + } + else + { + server.StartServer(); + } - // Register the server side handler (always pass true for server) - RegisterHandlers(server, true); + hooks = new MultiInstanceHooks(); + server.ConnectionManager.MessageManager.Hook(hooks); + s_Hooks[server] = hooks; - callback?.Invoke(); + // Register the server side handler (always pass true for server) + RegisterHandlers(server, true); + callback?.Invoke(); + } for (int i = 0; i < clients.Length; i++) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs index f658fcbcc4..5967d49488 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs @@ -311,8 +311,11 @@ private unsafe void VerifyArrayEquality(NativeList value, byte* unsafePtr, { int* sizeValue = (int*)(unsafePtr + offset); Assert.AreEqual(value.Length, *sizeValue); - +#if UTP_TRANSPORT_2_0_ABOVE + var asTPointer = value.GetUnsafePtr(); +#else var asTPointer = (T*)value.GetUnsafePtr(); +#endif var underlyingTArray = (T*)(unsafePtr + sizeof(int) + offset); for (var i = 0; i < value.Length; ++i) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs index 5470f21656..e072f3c956 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs @@ -2,7 +2,9 @@ using NUnit.Framework; using Unity.Collections; using Unity.Netcode.Transports.UTP; +#if !UTP_TRANSPORT_2_0_ABOVE using Unity.Networking.Transport; +#endif namespace Unity.Netcode.EditorTests { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs index c1550da95f..ca6a354b96 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs @@ -2,7 +2,9 @@ using NUnit.Framework; using Unity.Collections; using Unity.Netcode.Transports.UTP; +#if !UTP_TRANSPORT_2_0_ABOVE using Unity.Networking.Transport; +#endif namespace Unity.Netcode.EditorTests { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs index 0300f04dbe..0cf96580f6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs @@ -26,9 +26,9 @@ public ConnectionApprovalTimeoutTests(ApprovalTimedOutTypes approvalFailureType) m_ApprovalFailureType = approvalFailureType; } - // Must be >= 2 since this is an int value and the test waits for timeout - 1 to try to verify it doesn't + // Must be >= 5 since this is an int value and the test waits for timeout - 1 to try to verify it doesn't // time out early - private const int k_TestTimeoutPeriod = 1; + private const int k_TestTimeoutPeriod = 5; private Regex m_ExpectedLogMessage; private LogType m_LogType; @@ -59,6 +59,7 @@ protected override IEnumerator OnStartedServerAndClients() { if (m_ApprovalFailureType == ApprovalTimedOutTypes.ServerDoesNotRespond) { + m_ServerNetworkManager.ConnectionManager.MockSkippingApproval = true; // We catch (don't process) the incoming approval message to simulate the server not sending the approved message in time m_ClientNetworkManagers[0].ConnectionManager.MessageManager.Hook(new MessageCatcher(m_ClientNetworkManagers[0])); m_ExpectedLogMessage = new Regex("Timed out waiting for the server to approve the connection request."); @@ -80,18 +81,18 @@ protected override IEnumerator OnStartedServerAndClients() [UnityTest] public IEnumerator ValidateApprovalTimeout() { - // Delay for half of the wait period - yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.5f); + // Just delay for a second + yield return new WaitForSeconds(1); // Verify we haven't received the time out message yet NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage); - yield return new WaitForSeconds(k_TestTimeoutPeriod * 1.5f); + yield return new WaitForSeconds(k_TestTimeoutPeriod * 1.25f); // We should have the test relative log message by this time. NetcodeLogAssert.LogWasReceived(m_LogType, m_ExpectedLogMessage); - Debug.Log("Checking connected client count"); + VerboseDebug("Checking connected client count"); // It should only have the host client connected Assert.AreEqual(1, m_ServerNetworkManager.ConnectedClients.Count, $"Expected only one client when there were {m_ServerNetworkManager.ConnectedClients.Count} clients connected!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index f85a9cbd41..1219f53518 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -5,6 +5,7 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; + using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests @@ -84,12 +85,19 @@ public int DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType return 0; } - +#if NGO_DAMODE + public override void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType) +#else public override void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context) +#endif { OnBeforeDefer?.Invoke(this, key); DeferMessageCalled = true; +#if NGO_DAMODE + base.DeferMessage(trigger, key, reader, ref context, messageType); +#else base.DeferMessage(trigger, key, reader, ref context); +#endif } public override void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key) @@ -203,6 +211,11 @@ public class DeferredMessagingTest : NetcodeIntegrationTest protected override void OnInlineSetup() { +#if NGO_DAMODE + // Revert back to standard deferred message format for tests (for now) + DeferredMessageManager.IncludeMessageType = false; +#endif + DeferredMessageTestRpcAndNetworkVariableComponent.ClientInstances.Clear(); DeferredMessageTestRpcComponent.ClientInstances.Clear(); DeferredMessageTestNetworkVariableComponent.ClientInstances.Clear(); @@ -894,7 +907,7 @@ public void WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Valu foreach (var unused in m_ClientNetworkManagers) { - LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} with key {serverObject.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); + LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} associated with id ({serverObject.GetComponent().NetworkObjectId}), but the {nameof(NetworkObject)} was not received within the timeout period {timeout} second(s)."); } int purgeCount = 0; @@ -904,7 +917,7 @@ public void WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Valu { ++purgeCount; var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start; - Debug.Log(client.RealTimeProvider.GetType().FullName); + VerboseDebug(client.RealTimeProvider.GetType().FullName); Assert.GreaterOrEqual(elapsed, timeout); Assert.AreEqual(1, manager.DeferredMessageCountTotal()); Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn)); @@ -990,7 +1003,7 @@ public void WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanTheConfigu foreach (var unused in m_ClientNetworkManagers) { - LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} with key {serverObject.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); + LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} associated with id ({serverObject.GetComponent().NetworkObjectId}), but the {nameof(NetworkObject)} was not received within the timeout period {timeout} second(s)."); } int purgeCount = 0; @@ -1095,9 +1108,8 @@ public void WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThanTheConf foreach (var unused in m_ClientNetworkManagers) { - - LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} with key {serverObject.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); - LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} with key {serverObject2.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); + LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} associated with id ({serverObject.GetComponent().NetworkObjectId}), but the {nameof(NetworkObject)} was not received within the timeout period {timeout} second(s)."); + LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} associated with id ({serverObject2.GetComponent().NetworkObjectId}), but the {nameof(NetworkObject)} was not received within the timeout period {timeout} second(s)."); } int purgeCount = 0; @@ -1188,7 +1200,7 @@ public void WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRemoved([ foreach (var unused in m_ClientNetworkManagers) { - LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} with key {serverObject.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); + LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} associated with id ({serverObject.GetComponent().NetworkObjectId}), but the {nameof(NetworkObject)} was not received within the timeout period {timeout} second(s)."); } int purgeCount = 0; @@ -1271,7 +1283,11 @@ public void WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObjectsAreNot Assert.AreEqual(0, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject2.GetComponent().NetworkObjectId)); } - serverObject2.GetComponent().ChangeOwnership(m_ServerNetworkManager.LocalClientId); + // KITTY-TODO: Review this change please: + // Changing ownership when the owner specified is already an owner should not send any messages + // The original test was changing ownership to the server when the object was spawned with the server being an owner. + //serverObject2.GetComponent().ChangeOwnership(m_ServerNetworkManager.LocalClientId); + serverObject2.GetComponent().ChangeOwnership(m_ClientNetworkManagers[1].LocalClientId); WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) @@ -1285,7 +1301,7 @@ public void WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObjectsAreNot foreach (var unused in m_ClientNetworkManagers) { - LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} with key {serverObject.GetComponent().NetworkObjectId}, but that trigger was not received within within {timeout} second(s)."); + LogAssert.Expect(LogType.Warning, $"[Netcode] Deferred messages were received for a trigger of type {IDeferredNetworkMessageManager.TriggerType.OnSpawn} associated with id ({serverObject.GetComponent().NetworkObjectId}), but the {nameof(NetworkObject)} was not received within the timeout period {timeout} second(s)."); } int purgeCount = 0; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority.meta new file mode 100644 index 0000000000..ee02283755 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 421e3306c97e10f47b9efb6101a998ee +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs new file mode 100644 index 0000000000..7a777a7d46 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs @@ -0,0 +1,268 @@ +#if NGO_DAMODE +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + + +namespace Unity.Netcode.RuntimeTests +{ + public class DeferredDespawningTests : IntegrationTestWithApproximation + { + private const int k_DaisyChainedCount = 5; + protected override int NumberOfClients => 2; + private List m_DaisyChainedDespawnObjects = new List(); + private List m_HasReachedEnd = new List(); + + public DeferredDespawningTests() : base(HostOrServer.DAHost) + { + } + + protected override void OnServerAndClientsCreated() + { + var daisyChainPrevious = (DeferredDespawnDaisyChained)null; + for (int i = 0; i < k_DaisyChainedCount; i++) + { + var daisyChainNode = CreateNetworkObjectPrefab($"Daisy-{i}"); + var daisyChainBehaviour = daisyChainNode.AddComponent(); + daisyChainBehaviour.IsRoot = i == 0; + if (daisyChainPrevious != null) + { + daisyChainPrevious.PrefabToSpawnWhenDespawned = daisyChainBehaviour.gameObject; + } + m_DaisyChainedDespawnObjects.Add(daisyChainNode); + + daisyChainPrevious = daisyChainBehaviour; + } + + base.OnServerAndClientsCreated(); + } + + + + [UnityTest] + public IEnumerator DeferredDespawning() + { + DeferredDespawnDaisyChained.EnableVerbose = m_EnableVerboseDebug; + var rootInstance = SpawnObject(m_DaisyChainedDespawnObjects[0], m_ServerNetworkManager); + DeferredDespawnDaisyChained.ReachedLastChainInstance = ReachedLastChainObject; + var timeoutHelper = new TimeoutHelper(300); + yield return WaitForConditionOrTimeOut(HaveAllClientsReachedEndOfChain, timeoutHelper); + AssertOnTimeout($"Timed out waiting for all children to reach the end of their chained deferred despawns!", timeoutHelper); + } + + private bool HaveAllClientsReachedEndOfChain() + { + if (!m_HasReachedEnd.Contains(m_ServerNetworkManager.LocalClientId)) + { + return false; + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!m_HasReachedEnd.Contains(client.LocalClientId)) + { + return false; + } + } + return true; + } + + private void ReachedLastChainObject(ulong clientId) + { + m_HasReachedEnd.Add(clientId); + } + } + + /// + /// This helper behaviour handles the majority of the validation for deferred despawning. + /// Each instance triggers a series of deferred despawns where the owner validates the + /// NetworkVariables are updated and spawns another prefab prior to despawning locally + /// and the non-owners validate receiving the NetworkVariable change notification which + /// contains a reference to a DeferredDespawnDaisyChained component on the newly spawned + /// prefab driven by the authority. This repeats for the number specified in the integration + /// test. + /// + public class DeferredDespawnDaisyChained : NetworkBehaviour + { + public static bool EnableVerbose; + public static Action ReachedLastChainInstance; + private const int k_StartingDeferTick = 4; + public static Dictionary> ClientRelativeInstances = new Dictionary>(); + public bool IsRoot; + public GameObject PrefabToSpawnWhenDespawned; + public bool WasContactedByPeviousChainMember { get; private set; } + public int DeferDespawnTick { get; private set; } + + private void PingInstance() + { + WasContactedByPeviousChainMember = true; + } + + /// + /// This hits two birds with one NetworkVariable: + /// - Validates that NetworkVariables modified while the authority is in the middle of deferring a despawn are serialized and received by non-authority instances. + /// - Validates that the non-authority instances receive the updates within the deferred tick period of time and can use them to handle other visual synchronization + /// realted tasks (or the like). + /// + private NetworkVariable m_ValidateDirtyNetworkVarUpdate = new NetworkVariable(); + + private DeferredDespawnDaisyChained m_NextNodeSpawned = null; + + private void FailTest(string msg) + { + Assert.Fail($"[{nameof(DeferredDespawnDaisyChained)}][Client-{NetworkManager.LocalClientId}] {msg}"); + } + + public override void OnNetworkSpawn() + { + var localId = NetworkManager.LocalClientId; + if (!ClientRelativeInstances.ContainsKey(localId)) + { + ClientRelativeInstances.Add(localId, new Dictionary()); + } + + if (ClientRelativeInstances[localId].ContainsKey(NetworkObject.NetworkObjectId)) + { + FailTest($"[{nameof(OnNetworkSpawn)}] Client already has a table entry for NetworkObject-{NetworkObject.NetworkObjectId} | {name}!"); + } + + ClientRelativeInstances[localId].Add(NetworkObject.NetworkObjectId, this); + + if (!HasAuthority) + { + m_ValidateDirtyNetworkVarUpdate.OnValueChanged += OnValidateDirtyChanged; + } + + if (HasAuthority && IsRoot) + { + DeferDespawnTick = k_StartingDeferTick; + } + + base.OnNetworkSpawn(); + } + + private void OnValidateDirtyChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + { + if (!HasAuthority) + { + if (!current.TryGet(out m_NextNodeSpawned, NetworkManager)) + { + FailTest($"[{nameof(OnValidateDirtyChanged)}][{nameof(NetworkBehaviourReference)}] Failed to get the {nameof(DeferredDespawnDaisyChained)} behaviour from the {nameof(NetworkBehaviourReference)}!"); + } + + if (m_NextNodeSpawned.NetworkManager != NetworkManager) + { + FailTest($"[{nameof(NetworkManager)}][{nameof(NetworkBehaviourReference.TryGet)}] The {nameof(NetworkManager)} of {nameof(m_NextNodeSpawned)} does not match the local relative {nameof(NetworkManager)} instance!"); + } + } + } + + public override void OnNetworkDespawn() + { + if (!HasAuthority && !NetworkManager.ShutdownInProgress) + { + if (PrefabToSpawnWhenDespawned != null) + { + m_NextNodeSpawned.PingInstance(); + } + else + { + ReachedLastChainInstance?.Invoke(NetworkManager.LocalClientId); + } + } + base.OnNetworkDespawn(); + } + + private void InvokeDespawn() + { + if (!HasAuthority) + { + FailTest($"[{nameof(InvokeDespawn)}] Client is not the authority but this was invoked (integration test logic issue)!"); + } + NetworkObject.DeferDespawn(DeferDespawnTick); + } + + public override void OnDeferringDespawn(int despawnTick) + { + if (!HasAuthority) + { + FailTest($"[{nameof(OnDeferringDespawn)}] Client is not the authority but this was invoked (integration test logic issue)!"); + } + + if (despawnTick != (DeferDespawnTick + NetworkManager.ServerTime.Tick)) + { + FailTest($"[{nameof(OnDeferringDespawn)}] The passed in {despawnTick} parameter ({despawnTick}) does not equal the expected value of ({DeferDespawnTick + NetworkManager.ServerTime.Tick})!"); + } + + if (PrefabToSpawnWhenDespawned != null) + { + var deferNetworkObject = PrefabToSpawnWhenDespawned.GetComponent().InstantiateAndSpawn(NetworkManager); + var deferComponent = deferNetworkObject.GetComponent(); + // Slowly increment the despawn tick count as we process the chain of deferred despawns + deferComponent.DeferDespawnTick = DeferDespawnTick + 1; + // This should get updated on all non-authority instances before they despawn + m_ValidateDirtyNetworkVarUpdate.Value = new NetworkBehaviourReference(deferComponent); + } + else + { + ReachedLastChainInstance?.Invoke(NetworkManager.LocalClientId); + } + base.OnDeferringDespawn(despawnTick); + } + + private bool m_DeferredDespawn; + private void Update() + { + if (!IsSpawned || !HasAuthority || m_DeferredDespawn) + { + return; + } + + // Wait until all clients have this instance + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (!ClientRelativeInstances.ContainsKey(clientId)) + { + // exit early if the client doesn't exist yet + return; + } + + if (!ClientRelativeInstances[clientId].ContainsKey(NetworkObjectId)) + { + // exit early if the client hasn't spawned a clone of this instance yet + return; + } + + if (clientId == NetworkManager.LocalClientId) + { + continue; + } + + // This should happen shortly afte the instances spawns (based on the deferred despawn count) + if (!IsRoot && !ClientRelativeInstances[clientId][NetworkObjectId].WasContactedByPeviousChainMember) + { + // exit early if the non-authority instance has not been contacted yet + return; + } + } + + // If we made it here, then defer despawn this instance + InvokeDespawn(); + m_DeferredDespawn = true; + } + + private void Log(string message) + { + if (!EnableVerbose) + { + return; + } + Debug.Log($"[{name}][Client-{NetworkManager.LocalClientId}][{NetworkObjectId}] {message}"); + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs.meta new file mode 100644 index 0000000000..e7d54c6d9b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7b700919b058f446a75a398d5be9af4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs new file mode 100644 index 0000000000..1f0e3f62de --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -0,0 +1,522 @@ +#if NGO_DAMODE +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.UTP; +using UnityEngine; +using UnityEngine.TestTools; +using Random = UnityEngine.Random; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// Validates that distributable NetworkObjects are distributed upon + /// a client connecting or disconnecting. + /// + public class DistributeObjectsTests : IntegrationTestWithApproximation + { + private GameObject m_DistributeObject; + + private StringBuilder m_ErrorLog = new StringBuilder(); + + private const int k_LateJoinClientCount = 4; + protected override int NumberOfClients => 0; + + public DistributeObjectsTests() : base(HostOrServer.DAHost) + { + } + + protected override IEnumerator OnSetup() + { + m_ObjectToValidate = null; + return base.OnSetup(); + } + + protected override void OnServerAndClientsCreated() + { + var serverTransport = m_ServerNetworkManager.NetworkConfig.NetworkTransport as UnityTransport; + // I hate having to add time to our tests, but in case a VM is running slow the disconnect timeout needs to be reasonably high + serverTransport.DisconnectTimeoutMS = 1000; + m_DistributeObject = CreateNetworkObjectPrefab("DisObject"); + m_DistributeObject.AddComponent(); + m_DistributeObject.AddComponent(); + + // Set baseline to be distributable + var networkObject = m_DistributeObject.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); + networkObject.DontDestroyWithOwner = true; + base.OnServerAndClientsCreated(); + } + + protected override IEnumerator OnServerAndClientsConnected() + { + // DANGO-TODO: For now, logging all transfers of ownership due to client connect and disconnect. + m_ServerNetworkManager.SpawnManager.EnableDistributeLogging = true; + m_ServerNetworkManager.ConnectionManager.EnableDistributeLogging = true; + return base.OnServerAndClientsConnected(); + } + + private NetworkObject m_ObjectToValidate; + + private bool ValidateObjectSpawnedOnAllClients() + { + m_ErrorLog.Clear(); + + var networkObjectId = m_ObjectToValidate.NetworkObjectId; + var name = m_ObjectToValidate.name; + if (!UseCMBService() && !m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + m_ErrorLog.Append($"Client-{m_ServerNetworkManager.LocalClientId} has not spawned {name}!"); + return false; + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + m_ErrorLog.Append($"Client-{client.LocalClientId} has not spawned {name}!"); + return false; + } + } + return true; + } + + private const int k_ObjectCount = 20; + + private bool ValidateDistributedObjectsSpawned(bool lateJoining) + { + m_ErrorLog.Clear(); + var hostId = m_ServerNetworkManager.LocalClientId; + if (!DistributeObjectsTestHelper.DistributedObjects.ContainsKey(hostId)) + { + m_ErrorLog.AppendLine($"[Client-{hostId}] Does not have an entry in the root of the {nameof(DistributeObjectsTestHelper.DistributedObjects)} table!"); + return false; + } + var daHostObjectTracking = DistributeObjectsTestHelper.DistributedObjects[hostId]; + if (!daHostObjectTracking.ContainsKey(hostId)) + { + m_ErrorLog.AppendLine($"[Client-{hostId}] Does not have a local an entry in the {nameof(DistributeObjectsTestHelper.DistributedObjects)} table!"); + return false; + } + + var daHostObjects = daHostObjectTracking[hostId]; + var expected = 0; + if (lateJoining) + { + expected = k_ObjectCount / (m_ClientNetworkManagers.Count() + 1); + } + else + { + expected = k_ObjectCount / (m_ClientNetworkManagers.Where((c) => c.IsConnectedClient).Count() + 1); + } + + // It should theoretically be the expected or... + if (daHostObjects.Count != expected) + { + // due to not rounding one more than the expected + expected++; + if (daHostObjects.Count != expected) + { + m_ErrorLog.AppendLine($"[Client-{hostId}][General] Expected {expected} spawned objects, but only {daHostObjects.Count} exist!"); + return false; + } + } + + foreach (var networkObject in daHostObjects) + { + m_ObjectToValidate = networkObject.Value; + if (!ValidateObjectSpawnedOnAllClients()) + { + m_ErrorLog.AppendLine($"[{m_ObjectToValidate.name}] Was not spawned on all clients!"); + return false; + } + } + return true; + } + + private bool ValidateOwnershipTablesMatch() + { + m_ErrorLog.Clear(); + var hostId = m_ServerNetworkManager.LocalClientId; + var expectedEntries = m_ClientNetworkManagers.Count() + 1; + // Make sure all clients have an table created + if (DistributeObjectsTestHelper.DistributedObjects.Count < expectedEntries) + { + m_ErrorLog.AppendLine($"[General] Expected {expectedEntries} entries in the root of the {nameof(DistributeObjectsTestHelper.DistributedObjects)} table but only {DistributeObjectsTestHelper.DistributedObjects.Count} exist!"); + return false; + } + + if (!DistributeObjectsTestHelper.DistributedObjects.ContainsKey(hostId)) + { + m_ErrorLog.AppendLine($"[Client-{hostId}] Does not have an entry in the root of the {nameof(DistributeObjectsTestHelper.DistributedObjects)} table!"); + return false; + } + var daHostEntries = DistributeObjectsTestHelper.DistributedObjects[hostId]; + if (!daHostEntries.ContainsKey(hostId)) + { + m_ErrorLog.AppendLine($"[Client-{hostId}] Does not have a local an entry in the {nameof(DistributeObjectsTestHelper.DistributedObjects)} table!"); + return false; + } + var clients = m_ServerNetworkManager.ConnectedClientsIds.ToList(); + clients.Remove(0); + + // Cycle through each client's entry on the DAHost to run a comparison + foreach (var hostClientEntry in daHostEntries) + { + foreach (var ownerEntry in hostClientEntry.Value) + { + foreach (var client in clients) + { + var clientOwnerTable = DistributeObjectsTestHelper.DistributedObjects[client]; + if (!clientOwnerTable.ContainsKey(hostClientEntry.Key)) + { + m_ErrorLog.AppendLine($"[Client-{client}] No ownership table exists the client relative section of the {nameof(DistributeObjectsTestHelper.DistributedObjects)} table!"); + return false; + } + var clientEntry = clientOwnerTable[hostClientEntry.Key]; + if (!clientEntry.ContainsKey(ownerEntry.Key)) + { + m_ErrorLog.AppendLine($"[Client-{client}] {ownerEntry.Value.name} does not exists in Client-{client}'s sub-section for Owner-{hostClientEntry.Key} relative section of the {nameof(DistributeObjectsTestHelper.DistributedObjects)} table!"); + return false; + } + var clientObjectEntry = clientEntry[ownerEntry.Key]; + if (clientObjectEntry.OwnerClientId != ownerEntry.Value.OwnerClientId) + { + m_ErrorLog.AppendLine($"[Client-{client}][Owner Mismatch] {clientObjectEntry.OwnerClientId} does equal {ownerEntry.Value.OwnerClientId}!"); + return false; + } + // Assure the observers match + foreach (var observer in ownerEntry.Value.Observers) + { + if (!clientObjectEntry.Observers.Contains(observer)) + { + m_ErrorLog.AppendLine($"[Client-{client}][Observer Mismatch] {nameof(NetworkObject)} {clientObjectEntry.name}'s observers does not contain {observer}, but the authority instance does!"); + return false; + } + } + } + } + } + return true; + } + + private bool ValidateTransformsMatch() + { + m_ErrorLog.Clear(); + var hostId = m_ServerNetworkManager.LocalClientId; + var daHostEntries = DistributeObjectsTestHelper.DistributedObjects[hostId]; + var clients = m_ServerNetworkManager.ConnectedClientsIds.ToList(); + foreach (var clientOwner in daHostEntries.Keys) + { + // Cycle through the owner's objects + foreach (var entry in DistributeObjectsTestHelper.DistributedObjects[clientOwner][clientOwner].Values) + { + var ownerTestTransform = entry.GetComponent(); + // Compare against the other client instances of that object + foreach (var client in clients) + { + if (client == clientOwner) + { + continue; + } + + var clientObjectInstance = DistributeObjectsTestHelper.DistributedObjects[client][clientOwner][entry.NetworkObjectId]; + if (!ownerTestTransform.IsPositionClose(clientObjectInstance.transform.position)) + { + m_ErrorLog.AppendLine($"[Position Mismatch] Client-{client} Instance: {GetVector3Values(clientObjectInstance.transform.position)} != Owner Instance: {GetVector3Values(ownerTestTransform.transform.position)}!"); + return false; + } + } + } + } + return true; + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; + base.OnNewClientCreated(networkManager); + } + + private bool SpawnCountsMatch() + { + var passed = true; + var spawnCount = 0; + m_ErrorLog.Clear(); + if (!UseCMBService()) + { + spawnCount = m_ServerNetworkManager.SpawnManager.SpawnedObjects.Count; + } + else + { + spawnCount = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.Count; + } + foreach (var client in m_ClientNetworkManagers) + { + var clientCount = client.SpawnManager.SpawnedObjects.Count; + if (clientCount != spawnCount) + { + m_ErrorLog.AppendLine($"[Client-{client.LocalClientId}] Has a spawn count of {clientCount} but {spawnCount} was expected!"); + passed = false; + } + } + return passed; + } + + /// + /// This is a straight forward validation for the distribution of NetworkObjects + /// upon a client connecting or disconnecting. It also validates that the observers + /// on each non-authority instance matches the authority instance's. Finally, it + /// also includes validation that NetworkTransform updates continue to update and + /// synchronize properly after ownership for a set number of objects has changed. + /// + [UnityTest] + public IEnumerator DistributeNetworkObjects() + { + for (int i = 0; i < k_ObjectCount; i++) + { + SpawnObject(m_DistributeObject, m_ServerNetworkManager); + } + + // Validate NetworkObjects get redistributed properly when a client joins + for (int j = 0; j < k_LateJoinClientCount; j++) + { + yield return CreateAndStartNewClient(); + + yield return WaitForConditionOrTimeOut(() => ValidateDistributedObjectsSpawned(true)); + AssertOnTimeout($"[Client-{j + 1}][Initial Spawn] Not all clients spawned all objects!\n {m_ErrorLog}"); + + yield return WaitForConditionOrTimeOut(ValidateOwnershipTablesMatch); + AssertOnTimeout($"[Client-{j + 1}][OnwershipTable Mismatch] {m_ErrorLog}"); + + // When ownership changes, the new owner will randomly pick a new target to move towards and will move towards the target. + // Validate all other instances of the NetworkObjects that have had newly assigned owners have matching positions to the + // newly assigned owenr's instance. + yield return WaitForConditionOrTimeOut(ValidateTransformsMatch); + AssertOnTimeout($"[Client-{j + 1}][Transform Mismatch] {m_ErrorLog}"); + DisplayOwnership(); + + yield return WaitForConditionOrTimeOut(SpawnCountsMatch); + AssertOnTimeout($"[Spawn Count Mismatch] {m_ErrorLog}"); + } + + // Validate NetworkObjects get redistributed properly when a client disconnects + for (int j = k_LateJoinClientCount - 1; j >= 0; j--) + { + var client = m_ClientNetworkManagers[j]; + + // Remove the client from the other clients' ownership tracking table + DistributeObjectsTestHelper.RemoveClient(client.LocalClientId); + + // Disconnect the client + yield return StopOneClient(client); + + yield return new WaitForSeconds(0.3f); + + // Validate all tables match + yield return WaitForConditionOrTimeOut(ValidateOwnershipTablesMatch); + AssertOnTimeout($"[Client-{j + 1}][OnwershipTable Mismatch] {m_ErrorLog}"); + + // When ownership changes, the new owner will randomly pick a new target to move towards and will move towards the target. + // Validate all other instances of the NetworkObjects that have had newly assigned owners have matching positions to the + // newly assigned owenr's instance. + yield return WaitForConditionOrTimeOut(ValidateTransformsMatch); + AssertOnTimeout($"[Client-{j + 1}][Transform Mismatch] {m_ErrorLog}"); + + // DANGO-TODO: Make this tied to verbose mode once we know the CMB Service integration works properly + DisplayOwnership(); + + yield return WaitForConditionOrTimeOut(SpawnCountsMatch); + AssertOnTimeout($"[Spawn Count Mismatch] {m_ErrorLog}"); + } + } + + private void DisplayOwnership() + { + m_ErrorLog.Clear(); + var daHostEntries = DistributeObjectsTestHelper.DistributedObjects[0]; + + foreach (var entry in daHostEntries) + { + m_ErrorLog.AppendLine($"[Client-{entry.Key}][Owned Objects: {entry.Value.Count}]"); + } + + Debug.Log($"{m_ErrorLog}"); + } + + /// + /// This keeps track of each clients perspective of which NetworkObjects are owned by which client. + /// It is used to validate that all clients are in synch with ownership updates. + /// + public class DistributeObjectsTestHelper : NetworkBehaviour + { + /// + /// [Client Context][Client Owners][NetworkObjectId][NetworkObject] + /// + public static Dictionary>> DistributedObjects = new Dictionary>>(); + + public static void RemoveClient(ulong clientId) + { + foreach (var clients in DistributedObjects.Values) + { + clients.Remove(clientId); + } + DistributedObjects.Remove(clientId); + } + + internal ulong ClientId; + + public override void OnNetworkSpawn() + { + ClientId = NetworkManager.LocalClientId; + UpdateOwnerTableAdd(); + base.OnNetworkSpawn(); + } + + private void UpdateOwnerTableAdd() + { + if (!DistributedObjects.ContainsKey(ClientId)) + { + DistributedObjects.Add(ClientId, new Dictionary>()); + } + if (!DistributedObjects[ClientId].ContainsKey(OwnerClientId)) + { + DistributedObjects[ClientId].Add(OwnerClientId, new Dictionary()); + } + + if (DistributedObjects[ClientId][OwnerClientId].ContainsKey(NetworkObject.NetworkObjectId)) + { + throw new Exception($"[Client-{ClientId}][{name}] {nameof(NetworkObject)} already exists in Client-{ClientId}'s " + + $"DistributedObjects being tracking under Client-{OwnerClientId}'s list of owned {nameof(NetworkObject)}s!"); + } + DistributedObjects[ClientId][OwnerClientId].Add(NetworkObject.NetworkObjectId, NetworkObject); + } + + private void UpdateOwnerTableRemove(ulong previous) + { + // This does not need to exist when first starting, but will (at one point in testing) + // become valid. + if (DistributedObjects[ClientId].ContainsKey(previous)) + { + if (DistributedObjects[ClientId][previous].ContainsKey(NetworkObject.NetworkObjectId)) + { + DistributedObjects[ClientId][previous].Remove(NetworkObject.NetworkObjectId); + } + } + } + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + // At start, if NetworkSpawn has not been completed the local client ignores this + if (!DistributedObjects.ContainsKey(ClientId)) + { + return; + } + UpdateOwnerTableRemove(previous); + UpdateOwnerTableAdd(); + base.OnOwnershipChanged(previous, current); + } + } + + /// + /// This is used to validate that upon distributed ownership changes NetworkTransform sycnhronization + /// still works properly. + /// + public class DistributeTestTransform : NetworkTransform + { + private float m_DeltaVarPosition = 0.15f; + private float m_DeltaVarQauternion = 0.015f; + protected Vector3 GetRandomVector3(float min, float max, Vector3 baseLine, bool randomlyApplySign = false) + { + var retValue = new Vector3(baseLine.x * Random.Range(min, max), baseLine.y * Random.Range(min, max), baseLine.z * Random.Range(min, max)); + if (!randomlyApplySign) + { + return retValue; + } + + retValue.x *= Random.Range(1, 100) >= 50 ? -1 : 1; + retValue.y *= Random.Range(1, 100) >= 50 ? -1 : 1; + retValue.z *= Random.Range(1, 100) >= 50 ? -1 : 1; + return retValue; + } + + protected override bool OnIsServerAuthoritative() + { + var isOwnerAuth = base.OnIsServerAuthoritative(); + Assert.IsFalse(isOwnerAuth, $"Base {nameof(NetworkTransform)} did not automatically return false in distributed authority mode!"); + return isOwnerAuth; + } + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + + if (CanCommitToTransform) + { + var randomPos = GetRandomVector3(1.0f, 10.0f, Vector3.one, true); + SetState(randomPos, null, null, false); + m_TargetPosition = randomPos; + } + } + + private Vector3 m_TargetPosition; + private Vector3 m_DirToTarget; + private bool m_ReachedTarget; + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + base.OnOwnershipChanged(previous, current); + m_TargetPosition = transform.position + GetRandomVector3(4.0f, 8.0f, Vector3.one, true); + m_DirToTarget = (m_TargetPosition - transform.position).normalized; + m_ReachedTarget = false; + } + + protected override void Update() + { + base.Update(); + + if (CanCommitToTransform) + { + if (!m_ReachedTarget) + { + var distance = Vector3.Distance(transform.position, m_TargetPosition); + + var speed = Mathf.Clamp(distance, 0.10f, 2.0f); + + transform.position += m_DirToTarget * speed * Time.deltaTime; + + m_ReachedTarget = IsPositionClose(m_TargetPosition); + } + } + } + + public bool IsPositionClose(Vector3 position) + { + return Approximately(transform.position, position); + } + + protected bool Approximately(Vector3 a, Vector3 b) + { + var deltaVariance = m_DeltaVarPosition; + return Math.Round(Mathf.Abs(a.x - b.x), 2) <= deltaVariance && + Math.Round(Mathf.Abs(a.y - b.y), 2) <= deltaVariance && + Math.Round(Mathf.Abs(a.z - b.z), 2) <= deltaVariance; + } + + protected bool Approximately(Quaternion a, Quaternion b) + { + var deltaVariance = m_DeltaVarQauternion; + return Mathf.Abs(a.x - b.x) <= deltaVariance && + Mathf.Abs(a.y - b.y) <= deltaVariance && + Mathf.Abs(a.z - b.z) <= deltaVariance && + Mathf.Abs(a.w - b.w) <= deltaVariance; + } + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs.meta new file mode 100644 index 0000000000..b48a2d049f --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a759aeb53d12d842899381b411f3d2e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs new file mode 100644 index 0000000000..14b5d60868 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -0,0 +1,720 @@ +#if NGO_DAMODE +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using NUnit.Framework; +using Unity.Collections; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.UTP; +#if UTP_TRANSPORT_2_0_ABOVE +using Unity.Networking.Transport; +#endif +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + public class DistributedAuthorityCodecTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + // Use the CMB Service for all tests + protected override bool UseCMBService() => true; + + // Set the session mode to distributed authority for all tests + protected override SessionModeTypes OnGetSessionmode() => SessionModeTypes.DistributedAuthority; + + private CodecTestHooks m_ClientCodecHook; + private NetworkManager Client => m_ClientNetworkManagers[0]; + + private string m_TransportHost = Environment.GetEnvironmentVariable("NGO_HOST") ?? "127.0.0.1"; + private const int k_TransportPort = 7777; + private const int k_ClientId = 0; + + private GameObject m_SpawnObject; + + public class TestNetworkComponent : NetworkBehaviour + { + public NetworkList MyNetworkList = new NetworkList(new List { 1, 2, 3 }); + + [Rpc(SendTo.NotAuthority)] + public void TestNotAuthorityRpc(byte[] _) + { + } + + [Rpc(SendTo.Authority)] + public void TestAuthorityRpc(byte[] _) + { + } + } + + protected override void OnOneTimeSetup() + { + // Prevents the tests from running if no CMB Service is detected +#if !UTP_TRANSPORT_2_0_ABOVE + Assert.Ignore("ignoring DA codec tests because UTP transport must be 2.0"); +#else + if (!CanConnectToServer(m_TransportHost, k_TransportPort)) + { + Assert.Ignore("ignoring DA codec tests because UTP transport cannot connect to the runtime"); + } +#endif + base.OnOneTimeSetup(); + } + + /// + /// Add any additional components to default player prefab + /// + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + + /// + /// Modify NetworkManager instances for settings specific to tests + /// + protected override void OnServerAndClientsCreated() + { + var utpTransport = Client.gameObject.AddComponent(); + Client.NetworkConfig.NetworkTransport = utpTransport; + Client.NetworkConfig.EnableSceneManagement = false; + Client.NetworkConfig.AutoSpawnPlayerPrefabClientSide = true; + utpTransport.ConnectionData.Address = Dns.GetHostAddresses(m_TransportHost).First().ToString(); + utpTransport.ConnectionData.Port = k_TransportPort; + Client.LogLevel = LogLevel.Developer; + + // Validate we are in distributed authority mode with client side spawning and using CMB Service + Assert.True(Client.DistributedAuthorityMode, "Distributed authority is not set!"); + Assert.True(Client.AutoSpawnPlayerPrefabClientSide, "Client side spawning is not set!"); + Assert.True(Client.CMBServiceConnection, "CMBServiceConnection is not set!"); + + // Create a prefab for creating and destroying tests (auto-registers with NetworkManagers) + m_SpawnObject = CreateNetworkObjectPrefab("TestObject"); + m_SpawnObject.AddComponent(); + + // Ignore the client connection timeout after starting the client + m_BypassConnectionTimeout = true; + } + + protected override IEnumerator OnStartedServerAndClients() + { + // Register hooks after starting clients and server (in this case just the one client) + // We do this at this point in time because the MessageManager exists (happens within the same call stack when starting NetworkManagers) + m_ClientCodecHook = new CodecTestHooks(); + Client.MessageManager.Hook(m_ClientCodecHook); + yield return base.OnStartedServerAndClients(); + + // wait for client to connect since m_BypassConnectionTimeout + yield return WaitForConditionOrTimeOut(() => Client.LocalClient.PlayerObject != null); + AssertOnTimeout($"Timed out waiting for the client's player to be spanwed!"); + } + + [UnityTest] + public IEnumerator AuthorityRpc() + { + var player = Client.LocalClient.PlayerObject; + player.OwnerClientId = Client.LocalClientId + 1; + + var networkComponent = player.GetComponent(); + networkComponent.UpdateNetworkProperties(); + networkComponent.TestAuthorityRpc(new byte[] { 1, 2, 3, 4 }); + + // Universal Rpcs are sent as a ProxyMessage (which contains an RpcMessage) + yield return m_ClientCodecHook.WaitForMessageReceived(); + } + + [UnityTest] + public IEnumerator ChangeOwnership() + { + var message = new ChangeOwnershipMessage + { + DistributedAuthorityMode = true, + NetworkObjectId = 100, + OwnerClientId = 2, + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator ClientConnected() + { + var message = new ClientConnectedMessage() + { + ClientId = 2, + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator ClientDisconnected() + { + var message = new ClientDisconnectedMessage() + { + ClientId = 2, + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator CreateObject() + { + SpawnObject(m_SpawnObject, Client); + yield return m_ClientCodecHook.WaitForMessageReceived(); + } + + [UnityTest] + public IEnumerator DestroyObject() + { + var spawnedObject = SpawnObject(m_SpawnObject, Client); + yield return m_ClientCodecHook.WaitForMessageReceived(); + spawnedObject.GetComponent().Despawn(); + yield return m_ClientCodecHook.WaitForMessageReceived(); + } + + [UnityTest] + public IEnumerator Disconnect() + { + var message = new DisconnectReasonMessage + { + Reason = "test" + }; + + return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator NamedMessage() + { + var writeBuffer = new FastBufferWriter(sizeof(int), Allocator.Temp); + writeBuffer.WriteValueSafe(5); + + var message = new NamedMessage + { + Hash = 3, + SendData = writeBuffer, + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator NetworkVariableDelta() + { + var message = new NetworkVariableDeltaMessage + { + NetworkObjectId = 0, + NetworkBehaviourIndex = 1, + DeliveryMappedNetworkVariableIndex = new HashSet { 2, 3, 4 }, + TargetClientId = 5, + NetworkBehaviour = Client.LocalClient.PlayerObject.GetComponent(), + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator NotAuthorityRpc() + { + Client.LocalClient.PlayerObject.GetComponent().TestNotAuthorityRpc(new byte[] { 1, 2, 3, 4 }); + + // Universal Rpcs are sent as a ProxyMessage (which contains an RpcMessage) + yield return m_ClientCodecHook.WaitForMessageReceived(); + } + + [UnityTest] + public IEnumerator ParentSync() + { + var message = new ParentSyncMessage + { + NetworkObjectId = 0, + WorldPositionStays = true, + IsLatestParentSet = false, + Position = new Vector3(1, 2, 3), + Rotation = new Quaternion(4, 5, 6, 7), + Scale = new Vector3(8, 9, 10), + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SessionOwner() + { + var message = new SessionOwnerMessage() + { + SessionOwner = 2, + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator ServerLog() + { + var message = new ServerLogMessage() + { + LogType = NetworkLog.LogType.Info, + Message = "test", + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator UnnamedMessage() + { + var writeBuffer = new FastBufferWriter(sizeof(int), Allocator.Temp); + writeBuffer.WriteValueSafe(5); + + var message = new UnnamedMessage + { + SendData = writeBuffer, + }; + + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageLoad() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.Load, + LoadSceneMode = LoadSceneMode.Single, + SceneEventProgressId = Guid.NewGuid(), + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageLoadWithObjects() + { + Client.SceneManager.SkipSceneHandling = true; + var prefabNetworkObject = m_SpawnObject.GetComponent(); + + Client.SceneManager.ScenePlacedObjects.Add(0, new Dictionary() + { + { 1, prefabNetworkObject } + }); + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.Load, + LoadSceneMode = LoadSceneMode.Single, + SceneEventProgressId = Guid.NewGuid(), + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageUnload() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.Unload, + LoadSceneMode = LoadSceneMode.Single, + SceneEventProgressId = Guid.NewGuid(), + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageLoadComplete() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.LoadComplete, + LoadSceneMode = LoadSceneMode.Single, + SceneEventProgressId = Guid.NewGuid(), + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageUnloadComplete() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.UnloadComplete, + LoadSceneMode = LoadSceneMode.Single, + SceneEventProgressId = Guid.NewGuid(), + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageLoadCompleted() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.LoadEventCompleted, + LoadSceneMode = LoadSceneMode.Single, + SceneEventProgressId = Guid.NewGuid(), + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + ClientsCompleted = new List() { k_ClientId }, + ClientsTimedOut = new List() { 123456789 }, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageUnloadLoadCompleted() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.UnloadEventCompleted, + LoadSceneMode = LoadSceneMode.Single, + SceneEventProgressId = Guid.NewGuid(), + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + ClientsCompleted = new List() { k_ClientId }, + ClientsTimedOut = new List() { 123456789 }, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageSynchronize() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.Synchronize, + LoadSceneMode = LoadSceneMode.Single, + ClientSynchronizationMode = LoadSceneMode.Single, + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + ScenesToSynchronize = new Queue() + }; + eventData.ScenesToSynchronize.Enqueue(101); + eventData.SceneHandlesToSynchronize = new Queue(); + eventData.SceneHandlesToSynchronize.Enqueue(202); + + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageReSynchronize() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.ReSynchronize, + LoadSceneMode = LoadSceneMode.Single, + ClientSynchronizationMode = LoadSceneMode.Single, + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageSynchronizeComplete() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.ReSynchronize, + LoadSceneMode = LoadSceneMode.Single, + ClientSynchronizationMode = LoadSceneMode.Single, + SceneHash = XXHash.Hash32("SomeRandomSceneName"), + SceneHandle = 23456, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest] + public IEnumerator SceneEventMessageActiveSceneChanged() + { + Client.SceneManager.SkipSceneHandling = true; + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.ActiveSceneChanged, + ActiveSceneHash = XXHash.Hash32("ActiveScene") + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + [UnityTest, Ignore("Serializing twice causes data to disappear in the SceneManager for this event")] + public IEnumerator SceneEventMessageObjectSceneChanged() + { + Client.SceneManager.SkipSceneHandling = true; + var prefabNetworkObject = m_SpawnObject.GetComponent(); + Client.SceneManager.ObjectsMigratedIntoNewScene = new Dictionary>> + { + { 0, new Dictionary>()} + }; + + Client.SceneManager.ObjectsMigratedIntoNewScene[0].Add(Client.LocalClientId, new List() { prefabNetworkObject }); + var eventData = new SceneEventData(Client) + { + SceneEventType = SceneEventType.ObjectSceneChanged, + }; + + var message = new SceneEventMessage() + { + EventData = eventData + }; + yield return SendMessage(ref message); + } + + + private IEnumerator SendMessage(ref T message) where T : INetworkMessage + { + Client.MessageManager.SetVersion(k_ClientId, XXHash.Hash32(typeof(T).FullName), 0); + + var clientIds = new NativeArray(1, Allocator.Temp); + clientIds[0] = k_ClientId; + Client.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds); + Client.MessageManager.ProcessSendQueues(); + return m_ClientCodecHook.WaitForMessageReceived(message); + } + +#if UTP_TRANSPORT_2_0_ABOVE + private static bool CanConnectToServer(string host, ushort port, double timeoutMs = 100) + { + var address = Dns.GetHostAddresses(host).First(); + var endpoint = NetworkEndpoint.Parse(address.ToString(), port); + + var driver = NetworkDriver.Create(); + var connection = driver.Connect(endpoint); + + var start = DateTime.Now; + var ev = Networking.Transport.NetworkEvent.Type.Empty; + while (ev != Networking.Transport.NetworkEvent.Type.Connect) + { + driver.ScheduleUpdate().Complete(); + ev = driver.PopEventForConnection(connection, out _, out _); + + if (DateTime.Now - start > TimeSpan.FromMilliseconds(timeoutMs)) + { + return false; + } + } + + driver.Disconnect(connection); + return true; + } +#endif + } + + internal class CodecTestHooks : INetworkHooks + { + private Dictionary> m_ExpectedMessages = new Dictionary>(); + private Dictionary> m_ReceivedMessages = new Dictionary>(); + + private struct TestMessage + { + public string Name; + public byte[] Data; + } + + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage + { + if (message is ConnectionRequestMessage) + { + return; + } + + var writer = new FastBufferWriter(1024, Allocator.Temp); + message.Serialize(writer, 0); + + var testName = TestContext.CurrentContext.Test.Name; + if (!m_ExpectedMessages.ContainsKey(testName)) + { + m_ExpectedMessages[testName] = new Queue(); + } + + m_ExpectedMessages[testName].Enqueue(new TestMessage + { + Name = typeof(T).ToString(), + Data = writer.ToArray(), + }); + + writer.Dispose(); + } + + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage + { + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context) + { + if (messageType == typeof(ConnectionApprovedMessage)) + { + return true; + } + + var testName = TestContext.CurrentContext.Test.Name; + Assert.True(m_ExpectedMessages.ContainsKey(testName)); + Assert.IsNotEmpty(m_ExpectedMessages[testName]); + + var nextMessage = m_ExpectedMessages[testName].Dequeue(); + Assert.AreEqual(messageType.ToString(), nextMessage.Name, $"received unexpected message type: {messageType}"); + + if (!m_ReceivedMessages.ContainsKey(testName)) + { + m_ReceivedMessages[testName] = new HashSet(); + } + + m_ReceivedMessages[testName].Add(messageType.ToString()); + + // ServerLogMessage is an exception - it gets decoded correctly, but the bytes from the runtime do not directly match those sent by the SDK. + if (messageType == typeof(ServerLogMessage)) + { + return true; + } + + var expectedBytes = nextMessage.Data; + var receivedBytes = messageContent.ToArray(); + Assert.AreEqual(expectedBytes, receivedBytes); + + return true; + } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + + public IEnumerator WaitForMessageReceived(float timeout = 5) where T : INetworkMessage + { + var testName = TestContext.CurrentContext.Test.Name; + var messageType = typeof(T).FullName; + var startTime = Time.realtimeSinceStartup; + + while ((!m_ReceivedMessages.ContainsKey(testName) || !m_ReceivedMessages[testName].Contains(messageType)) && Time.realtimeSinceStartup - startTime < timeout) + { + yield return null; + } + + Assert.True(m_ReceivedMessages.ContainsKey(testName), "failed to receive any messages"); + Assert.True(m_ReceivedMessages[testName].Contains(messageType), $"failed to receive {messageType} message, received: {string.Join(", ", m_ReceivedMessages[testName])}"); + + // Reset received messages + m_ReceivedMessages[testName] = new HashSet(); + } + + public IEnumerator WaitForMessageReceived(T _, float timeout = 5) where T : INetworkMessage + { + return WaitForMessageReceived(timeout: timeout); + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs.meta new file mode 100644 index 0000000000..72b937fc8e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4cec6e6e1b9bdb746806290355d8cb50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs new file mode 100644 index 0000000000..4dc0f26bd6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -0,0 +1,300 @@ +#if NGO_DAMODE +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; +using Random = UnityEngine.Random; + + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.DAHost)] + public class NetworkClientAndPlayerObjectTests : NetcodeIntegrationTest + { + private const int k_PlayerPrefabCount = 6; + protected override int NumberOfClients => 2; + + private List m_PlayerPrefabs = new List(); + private Dictionary m_ChangedPlayerPrefabs = new Dictionary(); + + + public NetworkClientAndPlayerObjectTests(HostOrServer hostOrServer) : base(hostOrServer) + { + } + + protected override IEnumerator OnTearDown() + { + m_PlayerPrefabs.Clear(); + return base.OnTearDown(); + } + + protected override void OnServerAndClientsCreated() + { + m_PlayerPrefabs.Clear(); + for (int i = 0; i < k_PlayerPrefabCount; i++) + { + m_PlayerPrefabs.Add(CreateNetworkObjectPrefab($"PlayerPrefab{i}")); + } + base.OnServerAndClientsCreated(); + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; + if (m_DistributedAuthority) + { + networkManager.OnFetchLocalPlayerPrefabToSpawn = FetchPlayerPrefabToSpawn; + } + base.OnNewClientCreated(networkManager); + } + + /// + /// Only for distributed authority mode + /// + /// a unique player prefab for the player + private GameObject FetchPlayerPrefabToSpawn() + { + var prefabObject = GetRandomPlayerPrefab(); + var clientId = m_ClientNetworkManagers[m_ClientNetworkManagers.Length - 1].LocalClientId; + m_ChangedPlayerPrefabs.Add(clientId, prefabObject.GlobalObjectIdHash); + return prefabObject.gameObject; + } + + + private StringBuilder m_ErrorLogLevel3 = new StringBuilder(); + private StringBuilder m_ErrorLogLevel2 = new StringBuilder(); + private StringBuilder m_ErrorLogLevel1 = new StringBuilder(); + + private bool ValidateNetworkClient(NetworkClient networkClient) + { + m_ErrorLogLevel3.Clear(); + var success = true; + if (networkClient == null) + { + m_ErrorLogLevel3.Append($"[NetworkClient is NULL]"); + // Log error + success = false; + } + if (!networkClient.IsConnected) + { + m_ErrorLogLevel3.Append($"[NetworkClient {nameof(NetworkClient.IsConnected)}] is false]"); + // Log error + success = false; + } + if (networkClient.PlayerObject == null) + { + m_ErrorLogLevel3.Append($"[NetworkClient {nameof(NetworkClient.PlayerObject)}] is NULL]"); + // Log error + success = false; + } + return success; + } + + private bool ValidateNetworkManagerNetworkClients(NetworkManager networkManager) + { + var success = true; + m_ErrorLogLevel2.Clear(); + + // Number of connected clients plus the DAHost + var expectedCount = m_ClientNetworkManagers.Length + (m_UseHost ? 1 : 0); + + if (networkManager.ConnectedClients.Count != expectedCount) + { + m_ErrorLogLevel2.Append($"[{nameof(NetworkManager.ConnectedClients)} count: {networkManager.ConnectedClients.Count} vs expected count: {expectedCount}]"); + // Log error + success = false; + } + + if (m_UseHost && !ValidateNetworkClient(networkManager.LocalClient)) + { + m_ErrorLogLevel2.Append($"[Local NetworkClient: --({m_ErrorLogLevel3})--]"); + // Log error + success = false; + } + + foreach (var networkClient in networkManager.ConnectedClients) + { + // When just running a server, ignore the server's local NetworkClient + if (!m_UseHost && networkManager.IsServer) + { + continue; + } + if (!ValidateNetworkClient(networkManager.LocalClient)) + { + // Log error + success = false; + m_ErrorLogLevel2.Append($"[NetworkClient-{networkManager.LocalClientId}: --({m_ErrorLogLevel3})--]"); + } + } + return success; + } + + private bool AllNetworkClientsValidated() + { + m_ErrorLogLevel1.Clear(); + var success = true; + + if (!UseCMBService()) + { + if (!ValidateNetworkManagerNetworkClients(m_ServerNetworkManager)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}]{m_ErrorLogLevel2}"); + // Log error + success = false; + } + } + + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (!ValidateNetworkManagerNetworkClients(clientNetworkManager)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{clientNetworkManager.LocalClientId}]{m_ErrorLogLevel2}"); + // Log error + success = false; + } + } + return success; + } + + /// + /// Validates that all NetworkManager instances have valid NetworkClients for all connected clients + /// Validates the same thing when a client late joins and when a client disconnects. + /// + [UnityTest] + public IEnumerator ValidateNetworkClients() + { + // Validate the initial clients created + yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated); + AssertOnTimeout($"[Start] Not all NetworkClients were valid!\n{m_ErrorLogLevel1}"); + + // Late join a player and revalidate all instances + yield return CreateAndStartNewClient(); + yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated); + AssertOnTimeout($"[Late Join] Not all NetworkClients were valid!\n{m_ErrorLogLevel1}"); + + // Disconnect a player and revalidate all instances + var initialCount = m_ClientNetworkManagers.Length; + yield return StopOneClient(m_ClientNetworkManagers[m_ClientNetworkManagers.Length - 1], true); + // Sanity check to assure we removed the NetworkManager from m_ClientNetworkManagers + Assert.False(initialCount == m_ClientNetworkManagers.Length, $"Disconnected player and expected total number of client {nameof(NetworkManager)}s " + + $"to be {initialCount - 1} but it was still {initialCount}!"); + + yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated); + AssertOnTimeout($"[Client Disconnect] Not all NetworkClients were valid!\n{m_ErrorLogLevel1}"); + + } + + /// + /// Verify that all NetworkClients are pointing to the correct player object, even if + /// the player object is changed. + /// + private bool ValidatePlayerObjectOnClients(NetworkManager clientToValidate) + { + m_ErrorLogLevel2.Clear(); + var success = true; + var expectedGlobalObjectIdHash = m_ChangedPlayerPrefabs[clientToValidate.LocalClientId]; + if (expectedGlobalObjectIdHash != clientToValidate.LocalClient.PlayerObject.GlobalObjectIdHash) + { + m_ErrorLogLevel2.Append($"[Local Prefab Mismatch][Expected GlobalObjectIdHash: {expectedGlobalObjectIdHash} but was {clientToValidate.LocalClient.PlayerObject.GlobalObjectIdHash}]"); + success = false; + } + + foreach (var client in m_ClientNetworkManagers) + { + if (client == clientToValidate) + { + continue; + } + var remoteNetworkClient = client.ConnectedClients[clientToValidate.LocalClientId]; + if (expectedGlobalObjectIdHash != remoteNetworkClient.PlayerObject.GlobalObjectIdHash) + { + m_ErrorLogLevel2.Append($"[Client-{client.LocalClientId} Prefab Mismatch][Expected GlobalObjectIdHash: {expectedGlobalObjectIdHash} but was {remoteNetworkClient.PlayerObject.GlobalObjectIdHash}]"); + success = false; + } + } + return success; + } + + private bool ValidateAllPlayerObjects() + { + m_ErrorLogLevel1.Clear(); + var success = true; + if (m_UseHost && !UseCMBService()) + { + if (!ValidatePlayerObjectOnClients(m_ServerNetworkManager)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}]{m_ErrorLogLevel2}"); + success = false; + } + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!ValidatePlayerObjectOnClients(client)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}]{m_ErrorLogLevel2}"); + success = false; + } + } + + return success; + } + + private NetworkObject GetRandomPlayerPrefab() + { + return m_PlayerPrefabs[Random.Range(0, m_PlayerPrefabs.Count() - 1)].GetComponent(); + } + + /// + /// Validates that when a client changes their player object that all connected client instances mirror the + /// client's new player object. + /// + [UnityTest] + public IEnumerator ValidatePlayerObjects() + { + // Just do a quick validation for all connected client's NetworkClients + yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated); + AssertOnTimeout($"Not all NetworkClients were valid!\n{m_ErrorLogLevel1}"); + + // Now, have each client spawn a new player object + m_ChangedPlayerPrefabs.Clear(); + var playerInstance = (GameObject)null; + var playerPrefabToSpawn = (NetworkObject)null; + if (m_UseHost) + { + playerPrefabToSpawn = GetRandomPlayerPrefab(); + playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, m_ServerNetworkManager); + m_ChangedPlayerPrefabs.Add(m_ServerNetworkManager.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash); + } + + foreach (var client in m_ClientNetworkManagers) + { + playerPrefabToSpawn = GetRandomPlayerPrefab(); + playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, client); + m_ChangedPlayerPrefabs.Add(client.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash); + } + + // Validate that all connected clients' NetworkClient instances have the correct player object for each connected client + yield return WaitForConditionOrTimeOut(ValidateAllPlayerObjects); + AssertOnTimeout($"[Existing Clients] Not all NetworkClient player objects were valid!\n{m_ErrorLogLevel1}"); + + // Distributed authority only feature validation (NetworkManager.OnFetchLocalPlayerPrefabToSpawn) + if (m_DistributedAuthority) + { + // Now test the fetch prefab callback to assure that this is working correctly. + // Start a new client and wait for it to connect + yield return CreateAndStartNewClient(); + // Do another validation pass. + yield return WaitForConditionOrTimeOut(ValidateAllPlayerObjects); + AssertOnTimeout($"[Late Joined Client] Not all NetworkClient player objects were valid!\n{m_ErrorLogLevel1}"); + } + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs.meta new file mode 100644 index 0000000000..bacb51760b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db4c955c05fdc194eb47e7774b9c5101 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs new file mode 100644 index 0000000000..b5e9dd7f92 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -0,0 +1,408 @@ +#if NGO_DAMODE +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + public class OwnershipPermissionsTests : IntegrationTestWithApproximation + { + private GameObject m_PermissionsObject; + + private StringBuilder m_ErrorLog = new StringBuilder(); + + protected override int NumberOfClients => 4; + + public OwnershipPermissionsTests() : base(HostOrServer.DAHost) + { + } + + protected override IEnumerator OnSetup() + { + m_ObjectToValidate = null; + OwnershipPermissionsTestHelper.CurrentOwnedInstance = null; + return base.OnSetup(); + } + + protected override void OnServerAndClientsCreated() + { + m_PermissionsObject = CreateNetworkObjectPrefab("PermObject"); + m_PermissionsObject.AddComponent(); + + base.OnServerAndClientsCreated(); + } + + private NetworkObject m_ObjectToValidate; + + private bool ValidateObjectSpawnedOnAllClients() + { + m_ErrorLog.Clear(); + + var networkObjectId = m_ObjectToValidate.NetworkObjectId; + var name = m_ObjectToValidate.name; + if (!UseCMBService() && !m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + m_ErrorLog.Append($"Client-{m_ServerNetworkManager.LocalClientId} has not spawned {name}!"); + return false; + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + m_ErrorLog.Append($"Client-{client.LocalClientId} has not spawned {name}!"); + return false; + } + } + return true; + } + + private bool ValidatePermissionsOnAllClients() + { + var currentPermissions = (ushort)m_ObjectToValidate.Ownership; + var otherPermissions = (ushort)0; + var networkObjectId = m_ObjectToValidate.NetworkObjectId; + var objectName = m_ObjectToValidate.name; + m_ErrorLog.Clear(); + if (!UseCMBService()) + { + otherPermissions = (ushort)m_ServerNetworkManager.SpawnManager.SpawnedObjects[networkObjectId].Ownership; + if (currentPermissions != otherPermissions) + { + m_ErrorLog.Append($"Client-{m_ServerNetworkManager.LocalClientId} permissions for {objectName} is {otherPermissions} when it should be {currentPermissions}!"); + return false; + } + } + + foreach (var client in m_ClientNetworkManagers) + { + otherPermissions = (ushort)client.SpawnManager.SpawnedObjects[networkObjectId].Ownership; + if (currentPermissions != otherPermissions) + { + m_ErrorLog.Append($"Client-{client.LocalClientId} permissions for {objectName} is {otherPermissions} when it should be {currentPermissions}!"); + return false; + } + } + return true; + } + + private bool ValidateAllInstancesAreOwnedByClient(ulong clientId) + { + var networkObjectId = m_ObjectToValidate.NetworkObjectId; + var otherNetworkObject = (NetworkObject)null; + m_ErrorLog.Clear(); + if (!UseCMBService()) + { + otherNetworkObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[networkObjectId]; + if (otherNetworkObject.OwnerClientId != clientId) + { + m_ErrorLog.Append($"[Client-{m_ServerNetworkManager.LocalClientId}][{otherNetworkObject.name}] Expected owner to be {clientId} but it was {otherNetworkObject.OwnerClientId}!"); + return false; + } + } + + foreach (var client in m_ClientNetworkManagers) + { + otherNetworkObject = client.SpawnManager.SpawnedObjects[networkObjectId]; + if (otherNetworkObject.OwnerClientId != clientId) + { + m_ErrorLog.Append($"[Client-{client.LocalClientId}][{otherNetworkObject.name}] Expected owner to be {clientId} but it was {otherNetworkObject.OwnerClientId}!"); + return false; + } + } + return true; + } + + [UnityTest] + public IEnumerator ValidateOwnershipPermissionsTest() + { + var firstInstance = SpawnObject(m_PermissionsObject, m_ClientNetworkManagers[0]).GetComponent(); + OwnershipPermissionsTestHelper.CurrentOwnedInstance = firstInstance; + var firstInstanceHelper = firstInstance.GetComponent(); + var networkObjectId = firstInstance.NetworkObjectId; + m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance; + yield return WaitForConditionOrTimeOut(ValidateObjectSpawnedOnAllClients); + AssertOnTimeout($"[Failed To Spawn] {firstInstance.name}: \n {m_ErrorLog}"); + + // Validate the base non-assigned persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + ////////////////////////////////////// + // Setting & Removing Ownership Flags: + ////////////////////////////////////// + + // Now, cycle through all permissions and validate that when the owner changes them the change + // is synchronized on all non-owner clients. + foreach (var permissionObject in Enum.GetValues(typeof(NetworkObject.OwnershipStatus))) + { + var permission = (NetworkObject.OwnershipStatus)permissionObject; + // Add the status + firstInstance.SetOwnershipStatus(permission); + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Add][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + // Remove the status unless it is None (ignore None). + if (permission == NetworkObject.OwnershipStatus.None) + { + continue; + } + firstInstance.RemoveOwnershipStatus(permission); + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Remove][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + } + + //Add multiple flags at the same time + var multipleFlags = NetworkObject.OwnershipStatus.Transferable | NetworkObject.OwnershipStatus.Distributable | NetworkObject.OwnershipStatus.RequestRequired; + firstInstance.SetOwnershipStatus(multipleFlags, true); + Assert.IsTrue(firstInstance.HasOwnershipStatus(multipleFlags), $"[Set][Multi-flag Failure] Expected: {(ushort)multipleFlags} but was {(ushort)firstInstance.Ownership}!"); + + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + // Remove multiple flags at the same time + multipleFlags = NetworkObject.OwnershipStatus.Transferable | NetworkObject.OwnershipStatus.RequestRequired; + firstInstance.RemoveOwnershipStatus(multipleFlags); + // Validate the two flags no longer are set + Assert.IsFalse(firstInstance.HasOwnershipStatus(multipleFlags), $"[Remove][Multi-flag Failure] Expected: {(ushort)NetworkObject.OwnershipStatus.Distributable} but was {(ushort)firstInstance.Ownership}!"); + // Validate that the Distributable flag is still set + Assert.IsTrue(firstInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Distributable), $"[Remove][Multi-flag Failure] Expected: {(ushort)NetworkObject.OwnershipStatus.Distributable} but was {(ushort)firstInstance.Ownership}!"); + + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + ////////////////////// + // Changing Ownership: + ////////////////////// + + // Clear the flags, set the permissions to transferrable, and lock ownership in one pass. + firstInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable, true, NetworkObject.OwnershipLockActions.SetAndLock); + + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Reset][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + var secondInstance = m_ClientNetworkManagers[1].SpawnManager.SpawnedObjects[networkObjectId]; + var secondInstanceHelper = secondInstance.GetComponent(); + + secondInstance.ChangeOwnership(m_ClientNetworkManagers[1].LocalClientId); + Assert.IsTrue(secondInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.Locked, + $"Expected {secondInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.Locked} but its permission failure" + + $" status is {secondInstanceHelper.OwnershipPermissionsFailureStatus}!"); + + firstInstance.SetOwnershipLock(false); + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + // Sanity check to assure this client's instance isn't already the owner. + Assert.True(!secondInstance.IsOwner, $"[Ownership Check] Client-{m_ClientNetworkManagers[1].LocalClientId} already is the owner!"); + // Now try to acquire ownership + secondInstance.ChangeOwnership(m_ClientNetworkManagers[1].LocalClientId); + + // Validate the persmissions value for all instances are the same + yield return WaitForConditionOrTimeOut(() => secondInstance.IsOwner); + AssertOnTimeout($"[Acquire Ownership Failed] Client-{m_ClientNetworkManagers[1].LocalClientId} failed to get ownership!"); + + m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance; + // Validate all other client instances are showing the same owner + yield return WaitForConditionOrTimeOut(() => ValidateAllInstancesAreOwnedByClient(m_ClientNetworkManagers[1].LocalClientId)); + AssertOnTimeout($"[Ownership Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); + + // Clear the flags, set the permissions to RequestRequired, and lock ownership in one pass. + secondInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.RequestRequired, true); + + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); + + // Attempt to acquire ownership by just changing it + firstInstance.ChangeOwnership(firstInstance.NetworkManager.LocalClientId); + + // Assure we are denied ownership due to it requiring ownership be requested + Assert.IsTrue(firstInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.RequestRequired, + $"Expected {secondInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.RequestRequired} but its permission failure" + + $" status is {secondInstanceHelper.OwnershipPermissionsFailureStatus}!"); + + ////////////////////////////////// + // Test for single race condition: + ////////////////////////////////// + + // Start with a request for the client we expect to be given ownership + var requestStatus = firstInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + + // Get the 3rd client to send a request at the "relatively" same time + var thirdInstance = m_ClientNetworkManagers[2].SpawnManager.SpawnedObjects[networkObjectId]; + var thirdInstanceHelper = thirdInstance.GetComponent(); + + // At the same time send a request by the third client. + requestStatus = thirdInstance.RequestOwnership(); + + // We expect the 3rd client's request should be able to be sent at this time as well (i.e. creates the race condition between two clients) + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{m_ServerNetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + + // We expect the first requesting client to be given ownership + yield return WaitForConditionOrTimeOut(() => firstInstance.IsOwner); + AssertOnTimeout($"[Acquire Ownership Failed] Client-{firstInstance.NetworkManager.LocalClientId} failed to get ownership! ({firstInstanceHelper.OwnershipRequestResponseStatus})(Owner: {OwnershipPermissionsTestHelper.CurrentOwnedInstance.OwnerClientId}"); + m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance; + + // Just do a sanity check to assure ownership has changed on all clients. + yield return WaitForConditionOrTimeOut(() => ValidateAllInstancesAreOwnedByClient(firstInstance.NetworkManager.LocalClientId)); + AssertOnTimeout($"[Ownership Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + // Now, the third client should get a RequestInProgress returned as their request response + yield return WaitForConditionOrTimeOut(() => thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress); + AssertOnTimeout($"[Request In Progress Failed] Client-{thirdInstanceHelper.NetworkManager.LocalClientId} did not get the right request denied reponse!"); + + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + /////////////////////////////////////////////// + // Test for multiple ownership race conditions: + /////////////////////////////////////////////// + + // Get the 4th client's instance + var fourthInstance = m_ClientNetworkManagers[3].SpawnManager.SpawnedObjects[networkObjectId]; + var fourthInstanceHelper = fourthInstance.GetComponent(); + + // Send out a request from three clients at the same time + // The first one sent (and received for this test) gets ownership + requestStatus = secondInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + requestStatus = thirdInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + requestStatus = fourthInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + + // The 2nd and 3rd client should be denied and the 4th client should be approved + yield return WaitForConditionOrTimeOut(() => + (fourthInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress) && + (thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress) && + (secondInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Approved) + ); + AssertOnTimeout($"[Targeted Owner] Client-{secondInstanceHelper.NetworkManager.LocalClientId} did not get the right request denied reponse: {secondInstanceHelper.OwnershipRequestResponseStatus}!"); + m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance; + // Just do a sanity check to assure ownership has changed on all clients. + yield return WaitForConditionOrTimeOut(() => ValidateAllInstancesAreOwnedByClient(secondInstance.NetworkManager.LocalClientId)); + AssertOnTimeout($"[Ownership Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); + + // Validate the persmissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); + + /////////////////////////////////////////////// + // Test for targeted ownership request: + /////////////////////////////////////////////// + + // Now get the DAHost's client's instance + var daHostInstance = m_ServerNetworkManager.SpawnManager.SpawnedObjects[networkObjectId]; + var daHostInstanceHelper = daHostInstance.GetComponent(); + + secondInstanceHelper.AllowOwnershipRequest = true; + secondInstanceHelper.OnlyAllowTargetClientId = true; + secondInstanceHelper.ClientToAllowOwnership = daHostInstance.NetworkManager.LocalClientId; + + // Send out a request from all three clients + requestStatus = firstInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + requestStatus = thirdInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + requestStatus = fourthInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + requestStatus = daHostInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{daHostInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + + // The server and the 2nd client should be denied and the third client should be approved + yield return WaitForConditionOrTimeOut(() => + (firstInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && + (thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && + (fourthInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && + (daHostInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Approved) + ); + AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request reponse: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!"); + } + + public class OwnershipPermissionsTestHelper : NetworkBehaviour + { + public static NetworkObject CurrentOwnedInstance; + + public static Dictionary>> DistributedObjects = new Dictionary>>(); + + public bool AllowOwnershipRequest = true; + public bool OnlyAllowTargetClientId = false; + public ulong ClientToAllowOwnership; + + public NetworkObject.OwnershipRequestResponseStatus OwnershipRequestResponseStatus { get; private set; } + + public NetworkObject.OwnershipPermissionsFailureStatus OwnershipPermissionsFailureStatus { get; private set; } + + public NetworkObject.OwnershipRequestResponseStatus ExpectOwnershipRequestResponseStatus { get; set; } + + public override void OnNetworkSpawn() + { + NetworkObject.OnOwnershipRequested = OnOwnershipRequested; + NetworkObject.OnOwnershipRequestResponse = OnOwnershipRequestResponse; + NetworkObject.OnOwnershipPermissionsFailure = OnOwnershipPermissionsFailure; + + base.OnNetworkSpawn(); + } + + private bool OnOwnershipRequested(ulong clientId) + { + // If we are not allowing any client to request (without locking), then deny all requests + if (!AllowOwnershipRequest) + { + return false; + } + + // If we are only allowing a specific client and the requesting client is not the target, + // then deny the request + if (OnlyAllowTargetClientId && clientId != ClientToAllowOwnership) + { + return false; + } + + // Otherwise, approve the request + return true; + } + + private void OnOwnershipRequestResponse(NetworkObject.OwnershipRequestResponseStatus ownershipRequestResponseStatus) + { + OwnershipRequestResponseStatus = ownershipRequestResponseStatus; + } + + private void OnOwnershipPermissionsFailure(NetworkObject.OwnershipPermissionsFailureStatus ownershipPermissionsFailureStatus) + { + OwnershipPermissionsFailureStatus = ownershipPermissionsFailureStatus; + } + + public override void OnNetworkDespawn() + { + NetworkObject.OnOwnershipRequested = null; + NetworkObject.OnOwnershipRequestResponse = null; + base.OnNetworkSpawn(); + } + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + if (current == NetworkManager.LocalClientId) + { + CurrentOwnedInstance = NetworkObject; + } + base.OnOwnershipChanged(previous, current); + } + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs.meta new file mode 100644 index 0000000000..392a011586 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f35119aec96feb348a49b8e0fcd779de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs index 83d159714a..c87fe80976 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs @@ -22,13 +22,24 @@ public class HiddenVariableObject : NetworkBehaviour public static int ExpectedSize = 0; public static int SpawnCount = 0; + public static bool EnableVerbose; + + public static void VerboseDebug(string message) + { + if (!EnableVerbose) + { + return; + } + Debug.Log(message); + } + public override void OnNetworkSpawn() { if (!IsServer) { ClientInstancesSpawned.Add(NetworkObject); } - Debug.Log($"{nameof(HiddenVariableObject)}.{nameof(OnNetworkSpawn)}() with value {MyNetworkVariable.Value}"); + VerboseDebug($"{nameof(HiddenVariableObject)}.{nameof(OnNetworkSpawn)}() with value {MyNetworkVariable.Value}"); MyNetworkVariable.OnValueChanged += Changed; MyNetworkList.OnListChanged += ListChanged; @@ -48,7 +59,7 @@ public override void OnNetworkDespawn() public void Changed(int before, int after) { - Debug.Log($"Value changed from {before} to {after} on {NetworkManager.LocalClientId}"); + VerboseDebug($"Value changed from {before} to {after} on {NetworkManager.LocalClientId}"); ValueOnClient[NetworkManager.LocalClientId] = after; } public void ListChanged(NetworkListEvent listEvent) @@ -155,18 +166,19 @@ private IEnumerator SetAndCheckValueSet(ulong otherClientId, int value) yield return WaitForConditionOrTimeOut(VerifyLists); Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for all clients to have identical values!"); - Debug.Log("Value changed"); + VerboseDebug("Value changed"); } [UnityTest] public IEnumerator HiddenVariableTest() { + HiddenVariableObject.EnableVerbose = m_EnableVerboseDebug; HiddenVariableObject.SpawnCount = 0; HiddenVariableObject.ValueOnClient.Clear(); HiddenVariableObject.ExpectedSize = 0; HiddenVariableObject.SpawnCount = 0; - Debug.Log("Running test"); + VerboseDebug("Running test"); // ==== Spawn object with ownership on one client var client = m_ServerNetworkManager.ConnectedClientsList[1]; @@ -178,7 +190,7 @@ public IEnumerator HiddenVariableTest() // === Check spawn occurred yield return WaitForSpawnCount(NumberOfClients + 1); Debug.Assert(HiddenVariableObject.SpawnCount == NumberOfClients + 1); - Debug.Log("Objects spawned"); + VerboseDebug("Objects spawned"); // ==== Set the NetworkVariable value to 2 HiddenVariableObject.ExpectedSize = 1; @@ -207,7 +219,7 @@ public IEnumerator HiddenVariableTest() // ==== Wait for object to be spawned yield return WaitForSpawnCount(1); Debug.Assert(HiddenVariableObject.SpawnCount == 1); - Debug.Log("Object spawned"); + VerboseDebug("Object spawned"); // ==== We need a refresh for the newly re-spawned object yield return RefreshGameObects(4); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs index f88f70a441..9ce08c68ea 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs @@ -1,4 +1,5 @@ using System.Collections; +using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -45,6 +46,10 @@ public void Changed(NetworkListEvent listEvent) } } +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class NetworkListChangedTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -54,6 +59,10 @@ public class NetworkListChangedTests : NetcodeIntegrationTest private NetworkObject m_NetSpawnedObject1; +#if NGO_DAMODE + public NetworkListChangedTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("ListChangedObject"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs index 90e0be75c0..ace85293b6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs @@ -14,33 +14,6 @@ namespace Unity.Netcode.RuntimeTests /// public class NetVarContainer : NetworkBehaviour { - /// - /// Creates a prefab with two instances of this NetworkBehaviour - /// - /// - public static GameObject CreatePrefabGameObject(NetVarCombinationTypes netVarsToCheck) - { - var gameObject = new GameObject - { - // Always a good idea to name the Prefab for easy identification purposes - name = "NetVarContainerObject" - }; - var networkObject = gameObject.AddComponent(); - - // Create the two instances of the NetVarContainer components and add them to the - // GameObject of this prefab - var netVarContainer = gameObject.AddComponent(); - netVarContainer.NumberOfNetVarsToCheck = netVarsToCheck.FirstType; - netVarContainer.ValueToSetNetVarTo = NetworkBehaviourUpdaterTests.NetVarValueToSet; - netVarContainer = gameObject.AddComponent(); - netVarContainer.NumberOfNetVarsToCheck = netVarsToCheck.SecondType; - netVarContainer.ValueToSetNetVarTo = NetworkBehaviourUpdaterTests.NetVarValueToSet; - - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - - return gameObject; - } - public enum NetVarsToCheck { One, @@ -106,10 +79,20 @@ public bool AreNetVarsDirty() private NetworkVariable m_FirstValue = new NetworkVariable(); private NetworkVariable m_SeconValue = new NetworkVariable(); + public void SetOwnerWrite() + { + m_FirstValue = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + m_SeconValue = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + } + public override void OnNetworkSpawn() { - // Clients will register each NetworkObject when it is spawned + // Non-Authority will register each NetworkObject when it is spawned +#if NGO_DAMODE + if ((NetworkManager.DistributedAuthorityMode && !IsOwner) || (!NetworkManager.DistributedAuthorityMode && !IsServer)) +#else if (!IsServer) +#endif { NetworkBehaviourUpdaterTests.ClientSideNotifyObjectSpawned(gameObject); } @@ -121,7 +104,11 @@ public override void OnNetworkSpawn() /// public void SetNetworkVariableValues() { +#if NGO_DAMODE + if ((NetworkManager.DistributedAuthorityMode && IsOwner) || (!NetworkManager.DistributedAuthorityMode && IsServer)) +#else if (IsServer) +#endif { switch (NumberOfNetVarsToCheck) { @@ -153,90 +140,99 @@ public struct NetVarCombinationTypes public NetVarContainer.NetVarsToCheck SecondType; } + /// + /// Server and Distributed Authority modes require at least 1 client while the host does not. + /// + /// [Session Mode][Number of Clients][First NetVar Type][Second NetVar Type] +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] + [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.DAHost, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] + [TestFixture(HostOrServer.DAHost, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.DAHost, 2, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] +#endif + [TestFixture(HostOrServer.Server, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] + [TestFixture(HostOrServer.Server, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Server, 1, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Server, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] + [TestFixture(HostOrServer.Server, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Server, 2, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Host, 0, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] + [TestFixture(HostOrServer.Host, 0, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Host, 0, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Host, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] + [TestFixture(HostOrServer.Host, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Host, 1, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Host, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] + [TestFixture(HostOrServer.Host, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] + [TestFixture(HostOrServer.Host, 2, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] public class NetworkBehaviourUpdaterTests : NetcodeIntegrationTest { // Go ahead and create maximum number of clients (not all tests will use them) - protected override int NumberOfClients => 2; + protected override int NumberOfClients => m_NumberOfClients; public const int NetVarValueToSet = 1; private static List s_ClientSpawnedNetworkObjects = new List(); - private List m_ActiveClientsForCurrentTest; + private GameObject m_PrefabToSpawn; + private NetVarCombinationTypes m_NetVarCombinationTypes; + private int m_NumberOfClients = 0; - /// - /// Clients will call this when NetworkObjects are spawned on their end - /// - /// the GameObject of the NetworkObject spawned - public static void ClientSideNotifyObjectSpawned(GameObject objectSpaned) + public NetworkBehaviourUpdaterTests(HostOrServer hostOrServer, int numberOfClients, NetVarContainer.NetVarsToCheck first, NetVarContainer.NetVarsToCheck second) : base(hostOrServer) { - if (!s_ClientSpawnedNetworkObjects.Contains(objectSpaned)) + m_NetVarCombinationTypes = new NetVarCombinationTypes() { - s_ClientSpawnedNetworkObjects.Add(objectSpaned); - } + FirstType = first, + SecondType = second + }; + m_NumberOfClients = numberOfClients; } - protected override bool CanStartServerAndClients() + protected override IEnumerator OnSetup() { - return false; + s_ClientSpawnedNetworkObjects.Clear(); + return base.OnSetup(); } /// - /// Creates the server and client(s) required for this particular test iteration + /// Clients will call this when NetworkObjects are spawned on their end /// - private IEnumerator StartClientsAndServer(bool useHost, int numberOfClients, GameObject prefabObject) + /// the GameObject of the NetworkObject spawned + public static void ClientSideNotifyObjectSpawned(GameObject objectSpaned) { - void AddNetworkPrefab(NetworkConfig config, NetworkPrefab prefab) - { - config.Prefabs.Add(prefab); - } - - // Sanity check to make sure we are not trying to create more clients than we have available to use - Assert.True(numberOfClients <= m_ClientNetworkManagers.Length); - m_ActiveClientsForCurrentTest = new List(); - - // Create a list of the clients to be used in this test from the available clients - for (int i = 0; i < numberOfClients; i++) + if (!s_ClientSpawnedNetworkObjects.Contains(objectSpaned)) { - m_ActiveClientsForCurrentTest.Add(m_ClientNetworkManagers[i]); + s_ClientSpawnedNetworkObjects.Add(objectSpaned); } + } - // Add the prefab to be used for this particular test iteration - var np = new NetworkPrefab { Prefab = prefabObject }; - AddNetworkPrefab(m_ServerNetworkManager.NetworkConfig, np); - m_ServerNetworkManager.NetworkConfig.TickRate = 30; - foreach (var clientManager in m_ActiveClientsForCurrentTest) + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab("NetVarCont"); + // Create the two instances of the NetVarContainer components and add them to the + // GameObject of this prefab + var netVarContainer = m_PrefabToSpawn.AddComponent(); + netVarContainer.NumberOfNetVarsToCheck = m_NetVarCombinationTypes.FirstType; +#if NGO_DAMODE + if (m_SessionModeType == SessionModeTypes.DistributedAuthority) { - m_ServerNetworkManager.NetworkConfig.TickRate = 30; - AddNetworkPrefab(clientManager.NetworkConfig, np); + netVarContainer.SetOwnerWrite(); } +#endif + netVarContainer.ValueToSetNetVarTo = NetVarValueToSet; + netVarContainer = m_PrefabToSpawn.AddComponent(); - // Now spin everything up normally - var clientsAsArry = m_ActiveClientsForCurrentTest.ToArray(); - Assert.True(NetcodeIntegrationTestHelpers.Start(useHost, m_ServerNetworkManager, clientsAsArry), "Failed to start server and client instances"); - - // Only if we have clients (not host) - if (numberOfClients > 0) +#if NGO_DAMODE + if (m_SessionModeType == SessionModeTypes.DistributedAuthority) { - RegisterSceneManagerHandler(); + netVarContainer.SetOwnerWrite(); } +#endif + netVarContainer.NumberOfNetVarsToCheck = m_NetVarCombinationTypes.SecondType; + netVarContainer.ValueToSetNetVarTo = NetVarValueToSet; - // Wait for connection on client and server side - yield return WaitForClientsConnectedOrTimeOut(clientsAsArry); + base.OnServerAndClientsCreated(); } - /// - /// This list replaces the original NetworkVariable types to be checked. - /// Both NetworkVariables are of type int and the original version of this test was testing - /// the NetworkBehaviour Update when there were 1 or more (i.e two) on the same NetworkBehaviour. - /// After reviewing, we really only needed to test a much smaller combination of types and so - /// this pre-generated array represents the reduced set of combinations to test. - /// Note: - /// The original test was also testing for no NetworkVariables of type int, which there ended up - /// being no reason to do that and only added to the length of the execution time for this test. - /// - public static NetVarCombinationTypes[] NetVarCombinationTypeValues = new[]{ - new NetVarCombinationTypes() { FirstType = NetVarContainer.NetVarsToCheck.One, SecondType = NetVarContainer.NetVarsToCheck.One }, - new NetVarCombinationTypes() { FirstType = NetVarContainer.NetVarsToCheck.One, SecondType = NetVarContainer.NetVarsToCheck.Two }, - new NetVarCombinationTypes() { FirstType = NetVarContainer.NetVarsToCheck.Two, SecondType = NetVarContainer.NetVarsToCheck.Two }}; - /// /// The updated BehaviourUpdaterAllTests was re-designed to replicate the same functionality being tested in the /// original version of this test with additional time out handling and a re-organization in the order of operations. @@ -246,59 +242,39 @@ void AddNetworkPrefab(NetworkConfig config, NetworkPrefab prefab) /// version like the desktop tests use). /// This update also updated how the server and clients were being constructed to help reduce the execution time. /// - /// whether to run the server as a host or not - /// the NetworkVariable combination types - /// number of clients to use for the test + /// whether to run the server as a host or not /// number of NetworkObjects to be spawned [UnityTest] - public IEnumerator BehaviourUpdaterAllTests([Values] bool useHost, - [ValueSource(nameof(NetVarCombinationTypeValues))] NetVarCombinationTypes varCombinationTypes, - [Values(0, 1, 2)] int nbClients, [Values(1, 2)] int numToSpawn) + public IEnumerator BehaviourUpdaterAllTests([Values(1, 2)] int numToSpawn) { - s_ClientSpawnedNetworkObjects.Clear(); - - // The edge case scenario where we can exit early is when we are running - // just the server (i.e. non-host) and there are zero clients. Under this - // edge case scenario of the various combinations we do not need to run - // this test as the IsDirty flag is never cleared when no clients exist at all. - if (nbClients == 0 && !useHost) - { - yield break; - } - - // Create our prefab based on the NetVarCombinationTypes - var prefabToSpawn = NetVarContainer.CreatePrefabGameObject(varCombinationTypes); - - yield return StartClientsAndServer(useHost, nbClients, prefabToSpawn); - // Tracks the server-side spawned prefab instances var spawnedPrefabs = new List(); - var tickInterval = 1.0f / m_ServerNetworkManager.NetworkConfig.TickRate; // Used to determine if the client-side checks of this test should be // executed or not as well is used to make sure all clients have spawned // the appropriate number of NetworkObjects with the NetVarContainer behaviour - var numberOfObjectsToSpawnOnClients = numToSpawn * nbClients; + var numberOfObjectsToSpawn = numToSpawn * NumberOfClients; +#if NGO_DAMODE + var authority = m_SessionModeType == SessionModeTypes.DistributedAuthority ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; +#else + var authority = m_ServerNetworkManager; +#endif // spawn the objects for (int i = 0; i < numToSpawn; i++) { - var spawnedObject = Object.Instantiate(prefabToSpawn); + var spawnedObject = Object.Instantiate(m_PrefabToSpawn); spawnedPrefabs.Add(spawnedObject); var networkSpawnedObject = spawnedObject.GetComponent(); - networkSpawnedObject.NetworkManagerOwner = m_ServerNetworkManager; + networkSpawnedObject.NetworkManagerOwner = authority; networkSpawnedObject.Spawn(); } - // When there are no clients (excluding when server is in host mode), we can skip all of this - // wait until all objects are spawned on the clients - if (numberOfObjectsToSpawnOnClients > 0) - { - // Waits for all clients to spawn the NetworkObjects - yield return WaitForConditionOrTimeOut(() => numberOfObjectsToSpawnOnClients == s_ClientSpawnedNetworkObjects.Count); - Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for clients to report spawning objects! " + - $"Total reported client-side spawned objects {s_ClientSpawnedNetworkObjects.Count}"); - } + // Waits for all clients to spawn the NetworkObjects + yield return WaitForConditionOrTimeOut(() => numberOfObjectsToSpawn == s_ClientSpawnedNetworkObjects.Count); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for clients to report spawning objects! " + + $"Total reported client-side spawned objects {s_ClientSpawnedNetworkObjects.Count}"); + // Once all clients have spawned the NetworkObjects, set the network variables for // those NetworkObjects on the server-side. @@ -312,39 +288,33 @@ public IEnumerator BehaviourUpdaterAllTests([Values] bool useHost, } // Update the NetworkBehaviours to make sure all network variables are no longer marked as dirty - m_ServerNetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(); + authority.BehaviourUpdater.NetworkBehaviourUpdate(); // Verify that all network variables are no longer dirty on server side only if we have clients (including host) - foreach (var serverSpawnedObject in spawnedPrefabs) + foreach (var spawnedPrefab in spawnedPrefabs) { - var netVarContainers = serverSpawnedObject.GetComponents(); + var netVarContainers = spawnedPrefab.GetComponents(); foreach (var netVarContainer in netVarContainers) { Assert.False(netVarContainer.AreNetVarsDirty(), "Some NetworkVariables were still marked dirty after NetworkBehaviourUpdate!"); } } - // When there are no clients (excluding when server is in host mode), we can skip all of this - if (numberOfObjectsToSpawnOnClients > 0) + // Get a list of all NetVarContainer components on the client-side spawned NetworkObjects + var clientSideNetVarContainers = new List(); + foreach (var clientSpawnedObjects in s_ClientSpawnedNetworkObjects) { - // Get a list of all NetVarContainer components on the client-side spawned NetworkObjects - var clientSideNetVarContainers = new List(); - foreach (var clientSpawnedObjects in s_ClientSpawnedNetworkObjects) + var netVarContainers = clientSpawnedObjects.GetComponents(); + foreach (var netvarContiner in netVarContainers) { - var netVarContainers = clientSpawnedObjects.GetComponents(); - foreach (var netvarContiner in netVarContainers) - { - clientSideNetVarContainers.Add(netvarContiner); - } + clientSideNetVarContainers.Add(netvarContiner); } - - yield return WaitForConditionOrTimeOut(() => - clientSideNetVarContainers.Where(d => - d.HaveAllValuesChanged(NetVarValueToSet)).Count() == clientSideNetVarContainers.Count); - Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client side NetVarContainers to report all NetworkVariables have been updated!"); } - Object.DestroyImmediate(prefabToSpawn); + yield return WaitForConditionOrTimeOut(() => + clientSideNetVarContainers.Where(d => + d.HaveAllValuesChanged(NetVarValueToSet)).Count() == clientSideNetVarContainers.Count); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client side NetVarContainers to report all NetworkVariables have been updated!"); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 0e6d20e23f..cde1829f02 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -13,16 +13,25 @@ namespace Unity.Netcode.RuntimeTests /// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run . Client runs it. /// - Client destroy spawned => throw exception. /// + +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class NetworkObjectDestroyTests : NetcodeIntegrationTest { - protected override int NumberOfClients => 1; + protected override int NumberOfClients => 2; + +#if NGO_DAMODE + public NetworkObjectDestroyTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif /// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. /// /// [UnityTest] - public IEnumerator TestNetworkObjectServerDestroy() + public IEnumerator TestNetworkObjectAuthorityDestroy() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); @@ -35,15 +44,27 @@ public IEnumerator TestNetworkObjectServerDestroy() Assert.IsNotNull(serverClientPlayerResult.Result.gameObject); Assert.IsNotNull(clientClientPlayerResult.Result.gameObject); - // destroy the server player - Object.Destroy(serverClientPlayerResult.Result.gameObject); + var targetNetworkManager = m_ClientNetworkManagers[0]; +#if NGO_DAMODE + if (m_DistributedAuthority) + { + targetNetworkManager = m_ClientNetworkManagers[1]; + // destroy the authoritative player (distributed authority) + Object.Destroy(clientClientPlayerResult.Result.gameObject); + } + else +#endif + { + // destroy the authoritative player (client-server) + Object.Destroy(serverClientPlayerResult.Result.gameObject); + } - yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeHandled(m_ClientNetworkManagers[0]); + yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeHandled(targetNetworkManager); Assert.IsTrue(serverClientPlayerResult.Result == null); // Assert.IsNull doesn't work here Assert.IsTrue(clientClientPlayerResult.Result == null); - // create an unspawned networkobject and destroy it + // validate that any unspawned networkobject can be destroyed var go = new GameObject(); go.AddComponent(); Object.Destroy(go); @@ -74,16 +95,46 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c //destroying a NetworkObject while shutting down is allowed if (isShuttingDown) { - m_ClientNetworkManagers[0].Shutdown(); +#if NGO_DAMODE + if (m_DistributedAuthority) + { + // Shutdown the 2nd client + m_ClientNetworkManagers[1].Shutdown(); + } + else +#endif + { + // Shutdown the + m_ClientNetworkManagers[0].Shutdown(); + } } else { LogAssert.ignoreFailingMessages = true; NetworkLog.NetworkManagerOverride = m_ClientNetworkManagers[0]; } + m_ClientPlayerName = clientPlayer.gameObject.name; m_ClientNetworkObjectId = clientPlayer.NetworkObjectId; - Object.DestroyImmediate(clientPlayer.gameObject); +#if NGO_DAMODE + if (m_DistributedAuthority) + { + m_ClientPlayerName = m_PlayerNetworkObjects[m_ClientNetworkManagers[1].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject.name; + m_ClientNetworkObjectId = m_PlayerNetworkObjects[m_ClientNetworkManagers[1].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].NetworkObjectId; + + if (!isShuttingDown) + { + NetworkLog.NetworkManagerOverride = m_ClientNetworkManagers[1]; + } + // the 2nd client attempts to destroy the 1st client's player object (if shutting down then "ok" if not then not "ok") + Object.DestroyImmediate(m_PlayerNetworkObjects[m_ClientNetworkManagers[1].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject); + } + else +#endif + { + // the 1st client attempts to destroy its own player object (if shutting down then "ok" if not then not "ok") + Object.DestroyImmediate(m_ClientNetworkManagers[0].LocalClient.PlayerObject.gameObject); + } // destroying a NetworkObject while a session is active is not allowed if (!isShuttingDown) @@ -95,16 +146,39 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c private bool HaveLogsBeenReceived() { - if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) +#if NGO_DAMODE + if (m_DistributedAuthority) { - return false; + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-owner client is not valid during a distributed authority session. Call Destroy or Despawn on the client-owner instead.")) + { + return false; + } } - - if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode-Server Sender={m_ClientNetworkManagers[0].LocalClientId}] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + else { - return false; + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + { + return false; + } + + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode-Server Sender={m_ClientNetworkManagers[0].LocalClientId}] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + { + return false; + } } - +#else + { + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] Destroy a spawned NetworkObject on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + { + return false; + } + + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode-Server Sender={m_ClientNetworkManagers[0].LocalClientId}] Destroy a spawned NetworkObject on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + { + return false; + } + } +#endif return true; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs index 4300afddf6..78a92c15aa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs @@ -8,6 +8,9 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkObjectDontDestroyWithOwnerTests : NetcodeIntegrationTest @@ -36,6 +39,24 @@ public IEnumerator DontDestroyWithOwnerTest() yield return WaitForConditionOrTimeOut(() => client.SpawnManager.GetClientOwnedObjects(clientId).Count() == k_NumberObjectsToSpawn + 1); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client to have 33 NetworkObjects spawned! Only {client.SpawnManager.GetClientOwnedObjects(clientId).Count()} were assigned!"); +#if NGO_DAMODE + // Since clients spawn their objects locally in distributed authority mode, we have to rebuild the list of the client + // owned objects on the (DAHost) server-side because when the client disconnects it will destroy its local instances. + if (m_DistributedAuthority) + { + networkObjects.Clear(); + var serversideClientOwnedObjects = m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(clientId); + + foreach (var networkObject in serversideClientOwnedObjects) + { + if (!networkObject.IsPlayerObject) + { + networkObjects.Add(networkObject.gameObject); + } + } + } +#endif + // disconnect the client that owns all the clients NetcodeIntegrationTestHelpers.StopOneClient(client); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs index ae1ea072d5..3a89801374 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs @@ -10,12 +10,16 @@ namespace Unity.Netcode.RuntimeTests /// /// Tests that check OnNetworkDespawn being invoked /// + +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkObjectOnNetworkDespawnTests : NetcodeIntegrationTest { private const string k_ObjectName = "TestDespawn"; - public enum InstanceType + public enum InstanceTypes { Server, Client @@ -23,7 +27,10 @@ public enum InstanceType protected override int NumberOfClients => 1; private GameObject m_ObjectToSpawn; + private NetworkObject m_NetworkObject; + private HostOrServer m_HostOrServer; + public NetworkObjectOnNetworkDespawnTests(HostOrServer hostOrServer) : base(hostOrServer) { m_HostOrServer = hostOrServer; @@ -31,32 +38,17 @@ public NetworkObjectOnNetworkDespawnTests(HostOrServer hostOrServer) : base(host internal class OnNetworkDespawnTestComponent : NetworkBehaviour { - public static bool OnServerNetworkDespawnCalled { get; internal set; } - public static bool OnClientNetworkDespawnCalled { get; internal set; } + public bool OnNetworkDespawnCalled { get; internal set; } public override void OnNetworkSpawn() { - if (IsServer) - { - OnServerNetworkDespawnCalled = false; - } - else - { - OnClientNetworkDespawnCalled = false; - } + OnNetworkDespawnCalled = false; base.OnNetworkSpawn(); } public override void OnNetworkDespawn() { - if (IsServer) - { - OnServerNetworkDespawnCalled = true; - } - else - { - OnClientNetworkDespawnCalled = true; - } + OnNetworkDespawnCalled = true; base.OnNetworkDespawn(); } } @@ -68,46 +60,69 @@ protected override void OnServerAndClientsCreated() base.OnServerAndClientsCreated(); } - /// - /// This test validates that is invoked when the - /// is shutdown. - /// - [UnityTest] - public IEnumerator TestNetworkObjectDespawnOnShutdown() + private bool ObjectSpawnedOnAllNetworkManagerInstances() { - // Spawn the test object - var spawnedObject = SpawnObject(m_ObjectToSpawn, m_ServerNetworkManager); - var spawnedNetworkObject = spawnedObject.GetComponent(); + if (!s_GlobalNetworkObjects.ContainsKey(m_ServerNetworkManager.LocalClientId)) + { + return false; + } + if (!s_GlobalNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_NetworkObject.NetworkObjectId)) + { + return false; + } - // Wait for the client to spawn the object - yield return WaitForConditionOrTimeOut(() => + foreach (var clientNetworkManager in m_ClientNetworkManagers) { - if (!s_GlobalNetworkObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClientId)) + if (!s_GlobalNetworkObjects.ContainsKey(clientNetworkManager.LocalClientId)) { return false; } - if (!s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId].ContainsKey(spawnedNetworkObject.NetworkObjectId)) + if (!s_GlobalNetworkObjects[clientNetworkManager.LocalClientId].ContainsKey(m_NetworkObject.NetworkObjectId)) { return false; } - return true; - }); + } - AssertOnTimeout($"Timed out waiting for client to spawn {k_ObjectName}!"); + return true; + } + + /// + /// This test validates that is invoked when the + /// is shutdown. + /// + [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; +#if NGO_DAMODE + if (m_DistributedAuthority) + { + networkManagerOwner = networkManager; + } +#endif + + // Spawn the test object + var spawnedObject = SpawnObject(m_ObjectToSpawn, networkManagerOwner); + m_NetworkObject = spawnedObject.GetComponent(); + + + yield return WaitForConditionOrTimeOut(ObjectSpawnedOnAllNetworkManagerInstances); + AssertOnTimeout($"Timed out waiting for all {nameof(NetworkManager)} instances to spawn {m_NetworkObject.name}!"); + + // Get the spawned object relative to which NetworkManager instance we are testing. + var relativeSpawnedObject = s_GlobalNetworkObjects[networkManager.LocalClientId][m_NetworkObject.NetworkObjectId]; + var onNetworkDespawnTestComponent = relativeSpawnedObject.GetComponent(); // Confirm it is not set before shutting down the NetworkManager - Assert.IsFalse(OnNetworkDespawnTestComponent.OnClientNetworkDespawnCalled, "[Client-side] despawn state is already set (should not be set at this point)!"); - Assert.IsFalse(OnNetworkDespawnTestComponent.OnServerNetworkDespawnCalled, $"[{m_HostOrServer}-side] despawn state is already set (should not be set at this point)!"); - - // Shutdown the client-side first to validate the client-side instance invokes OnNetworkDespawn - m_ClientNetworkManagers[0].Shutdown(); - yield return WaitForConditionOrTimeOut(() => OnNetworkDespawnTestComponent.OnClientNetworkDespawnCalled); - AssertOnTimeout($"[Client-side] Timed out waiting for {k_ObjectName}'s {nameof(NetworkBehaviour.OnNetworkDespawn)} to be invoked!"); - - // Shutdown the servr-host-side second to validate servr-host-side instance invokes OnNetworkDespawn - m_ServerNetworkManager.Shutdown(); - yield return WaitForConditionOrTimeOut(() => OnNetworkDespawnTestComponent.OnClientNetworkDespawnCalled); - AssertOnTimeout($"[{m_HostOrServer}-side]Timed out waiting for {k_ObjectName}'s {nameof(NetworkBehaviour.OnNetworkDespawn)} to be invoked!"); + Assert.IsFalse(onNetworkDespawnTestComponent.OnNetworkDespawnCalled, $"{nameof(OnNetworkDespawnTestComponent.OnNetworkDespawnCalled)} was set prior to shutting down!"); + + // Shutdown the NetworkManager instance we are testing. + networkManager.Shutdown(); + + // Confirm that OnNetworkDespawn is invoked after shutdown + yield return WaitForConditionOrTimeOut(() => onNetworkDespawnTestComponent.OnNetworkDespawnCalled); + AssertOnTimeout($"Timed out waiting for {nameof(NetworkObject)} instance to despawn on the {despawnCheck} side!"); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 17b618a0a1..92a2dc7453 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -7,6 +7,10 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class NetworkObjectOnSpawnTests : NetcodeIntegrationTest { private GameObject m_TestNetworkObjectPrefab; @@ -27,23 +31,41 @@ public enum ObserverTestTypes private const string k_WithObserversError = "Not all clients spawned the"; private const string k_WithoutObserversError = "A client spawned the"; +#if NGO_DAMODE + public NetworkObjectOnSpawnTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + protected override void OnServerAndClientsCreated() { m_ObserverPrefab = CreateNetworkObjectPrefab(k_ObserverTestObjName); base.OnServerAndClientsCreated(); } - private bool CheckClientsSideObserverTestObj() { foreach (var client in m_ClientNetworkManagers) { - if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId)) + 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)) + { + return false; + } + } + else { - // When no observers there shouldn't be any client spawned NetworkObjects - // (players are held in a different list) - return !(m_ObserverTestType == ObserverTestTypes.WithObservers); + // 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)) + { + 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) @@ -83,10 +105,18 @@ protected override void OnNewClientCreated(NetworkManager networkManager) /// /// whether to spawn with or without observers [UnityTest] - public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes, [Values] bool sceneManagement) + public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes, [Values] SceneManagementState sceneManagement) { - if (!sceneManagement) + if (sceneManagement == SceneManagementState.SceneManagementDisabled) { +#if NGO_DAMODE + // When scene management is disabled, we need this wait period for all clients to be up to date with + // all other clients before beginning the process of stopping all clients. + if (m_DistributedAuthority) + { + yield return new WaitForSeconds(0.5f); + } +#endif // Disable prefabs to prevent them from being destroyed foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) { @@ -156,8 +186,10 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp m_ObserverTestType = ObserverTestTypes.WithoutObservers; // Just give a little time to make sure nothing spawned yield return s_DefaultWaitForTick; - yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj); - AssertOnTimeout($"{(withoutObservers ? k_WithoutObserversError : k_WithObserversError)} {k_ObserverTestObjName} object!"); + + // 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; + 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); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs new file mode 100644 index 0000000000..bdf265e3ca --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs @@ -0,0 +1,233 @@ +using System.Collections; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif + public class NetworkObjectOwnershipPropertiesTests : NetcodeIntegrationTest + { + private class DummyNetworkBehaviour : NetworkBehaviour + { + + } + + protected override int NumberOfClients => 2; + private GameObject m_PrefabToSpawn; + private NetworkObject m_OwnerSpawnedInstance; + private NetworkObject m_TargetOwnerInstance; + private NetworkManager m_InitialOwner; + private NetworkManager m_NextTargetOwner; + + private ulong m_InitialOwnerId; + private ulong m_TargetOwnerId; + private bool m_SpawnedInstanceIsOwner; + private bool m_InitialOwnerOwnedBySever; + private bool m_TargetOwnerOwnedBySever; + +#if NGO_DAMODE + public NetworkObjectOwnershipPropertiesTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + + protected override IEnumerator OnTearDown() + { + m_OwnerSpawnedInstance = null; + m_InitialOwner = null; + m_NextTargetOwner = null; + m_PrefabToSpawn = null; + return base.OnTearDown(); + } + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab("ClientOwnedObject"); + m_PrefabToSpawn.gameObject.AddComponent(); +#if NGO_DAMODE + m_PrefabToSpawn.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); +#endif + } + + public enum InstanceTypes + { + Server, + Client + } + + private StringBuilder m_OwnershipPropagatedFailures = new StringBuilder(); + private bool OwnershipPropagated() + { + var conditionMet = true; + m_OwnershipPropagatedFailures.Clear(); +#if NGO_DAMODE + // In distributed authority mode, we will check client owner to DAHost owner with InstanceTypes.Server and client owner to client + // when InstanceTypes.Client + if (m_DistributedAuthority) + { + if (!m_ClientNetworkManagers[1].SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) + { + conditionMet = false; + m_OwnershipPropagatedFailures.AppendLine($"Client-{m_ClientNetworkManagers[1].LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); + } + if (!m_ClientNetworkManagers[0].SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) + { + conditionMet = false; + m_OwnershipPropagatedFailures.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); + } + if (!m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) + { + conditionMet = false; + m_OwnershipPropagatedFailures.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); + } + } + else + { + if (m_NextTargetOwner != m_ServerNetworkManager) + { + if (!m_NextTargetOwner.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) + { + conditionMet = false; + m_OwnershipPropagatedFailures.AppendLine($"Client-{m_NextTargetOwner.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); + } + } + if (!m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) + { + conditionMet = false; + m_OwnershipPropagatedFailures.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); + } + } +#else + if (m_NextTargetOwner != m_ServerNetworkManager) + { + if (!m_NextTargetOwner.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) + { + conditionMet = false; + m_OwnershipPropagatedFailures.AppendLine($"Client-{m_NextTargetOwner.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); + } + } + if (!m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) + { + conditionMet = false; + m_OwnershipPropagatedFailures.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); + } +#endif + return conditionMet; + } + + private void ValidateOwnerShipProperties(bool targetIsOwner = false) + { + Assert.AreEqual(m_OwnerSpawnedInstance.IsOwner, m_SpawnedInstanceIsOwner); + Assert.AreEqual(m_OwnerSpawnedInstance.IsOwnedByServer, m_InitialOwnerOwnedBySever); + Assert.AreEqual(targetIsOwner ? m_TargetOwnerId : m_InitialOwnerId, m_OwnerSpawnedInstance.OwnerClientId); + + var initialOwnerBehaviour = m_OwnerSpawnedInstance.GetComponent(); + Assert.AreEqual(initialOwnerBehaviour.IsOwner, m_SpawnedInstanceIsOwner); + Assert.AreEqual(initialOwnerBehaviour.IsOwnedByServer, m_InitialOwnerOwnedBySever); + Assert.AreEqual(targetIsOwner ? m_TargetOwnerId : m_InitialOwnerId, initialOwnerBehaviour.OwnerClientId); + + Assert.AreEqual(m_TargetOwnerInstance.IsOwner, targetIsOwner); + Assert.AreEqual(m_TargetOwnerInstance.IsOwnedByServer, m_TargetOwnerOwnedBySever); + + Assert.AreEqual(targetIsOwner ? m_TargetOwnerId : m_InitialOwnerId, m_TargetOwnerInstance.OwnerClientId); + var targetOwnerBehaviour = m_TargetOwnerInstance.GetComponent(); + Assert.AreEqual(targetOwnerBehaviour.IsOwner, targetIsOwner); + Assert.AreEqual(targetOwnerBehaviour.IsOwnedByServer, m_TargetOwnerOwnedBySever); + Assert.AreEqual(targetIsOwner ? m_TargetOwnerId : m_InitialOwnerId, m_TargetOwnerInstance.OwnerClientId); + } + + + [UnityTest] + public IEnumerator ValidatePropertiesWithOwnershipChanges([Values(InstanceTypes.Server, InstanceTypes.Client)] InstanceTypes instanceType) + { + m_NextTargetOwner = instanceType == InstanceTypes.Server ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; + m_InitialOwner = instanceType == InstanceTypes.Client ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; +#if NGO_DAMODE + // In distributed authority mode, we will check client owner to DAHost owner with InstanceTypes.Server and client owner to client + // when InstanceTypes.Client + if (m_DistributedAuthority) + { + m_InitialOwner = m_ClientNetworkManagers[0]; + if (instanceType == InstanceTypes.Client) + { + m_NextTargetOwner = m_ClientNetworkManagers[1]; + } + m_PrefabToSpawn.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + } +#endif + m_InitialOwnerId = m_InitialOwner.LocalClientId; + m_TargetOwnerId = m_NextTargetOwner.LocalClientId; + m_InitialOwnerOwnedBySever = m_InitialOwner.IsServer; + m_TargetOwnerOwnedBySever = m_InitialOwner.IsServer; + var objectInstance = SpawnObject(m_PrefabToSpawn, m_InitialOwner); + + m_OwnerSpawnedInstance = objectInstance.GetComponent(); + m_SpawnedInstanceIsOwner = m_OwnerSpawnedInstance.NetworkManager == m_InitialOwner; + // Sanity check to verify that the next owner to target is not the owner of the spawned object + var hasEntry = m_InitialOwner.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId); + Assert.False(hasEntry); + + // Since CreateObjectMessage gets proxied by DAHost, just wait until the next target owner has the spawned instance in the s_GlobalNetworkObjects table. + yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects.ContainsKey(m_NextTargetOwner.LocalClientId) && s_GlobalNetworkObjects[m_NextTargetOwner.LocalClientId].ContainsKey(m_OwnerSpawnedInstance.NetworkObjectId)); + AssertOnTimeout($"Timed out waiting for Client-{m_NextTargetOwner.LocalClientId} to have an instance entry of {m_OwnerSpawnedInstance.name}-{m_OwnerSpawnedInstance.NetworkObjectId}!"); + + // Get the target client's instance of the spawned object + m_TargetOwnerInstance = s_GlobalNetworkObjects[m_NextTargetOwner.LocalClientId][m_OwnerSpawnedInstance.NetworkObjectId]; + + // Validate that NetworkObject and NetworkBehaviour ownership properties are correct + ValidateOwnerShipProperties(); + + // The authority always changes the ownership + // Client-Server: It will always be the host instance + // Distributed Authority: It can be either the DAHost or the client +#if NGO_DAMODE + if (m_DistributedAuthority) + { + // Use the target client's instance to change ownership + m_TargetOwnerInstance.ChangeOwnership(m_NextTargetOwner.LocalClientId); + if (instanceType == InstanceTypes.Client) + { + var networkManagersList = new System.Collections.Generic.List() { m_ServerNetworkManager, m_ClientNetworkManagers[0] }; + // Provide enough time for the client to receive and process the spawned message. + yield return WaitForMessageReceived(networkManagersList); + } + else + { + // Provide enough time for the client to receive and process the change in ownership message. + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + } + } + else + { + m_OwnerSpawnedInstance.ChangeOwnership(m_NextTargetOwner.LocalClientId); + // Provide enough time for the client to receive and process the change in ownership message. + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); + } +#else + m_OwnerSpawnedInstance.ChangeOwnership(m_NextTargetOwner.LocalClientId); + // Provide enough time for the client to receive and process the change in ownership message. + yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); +#endif + + // Ensure it's the ownership tables are updated + yield return WaitForConditionOrTimeOut(OwnershipPropagated); + AssertOnTimeout($"Timed out waiting for ownership to propagate!\n{m_OwnershipPropagatedFailures}"); + + m_SpawnedInstanceIsOwner = m_OwnerSpawnedInstance.NetworkManager == m_NextTargetOwner; + if (m_SpawnedInstanceIsOwner) + { + m_InitialOwnerOwnedBySever = m_OwnerSpawnedInstance.NetworkManager.IsServer; + } + m_InitialOwnerOwnedBySever = m_NextTargetOwner.IsServer; + m_TargetOwnerOwnedBySever = m_NextTargetOwner.IsServer; + + // Validate that NetworkObject and NetworkBehaviour ownership properties are correct + ValidateOwnerShipProperties(true); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index 5fa49158e7..a64efaa770 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -10,6 +10,8 @@ namespace Unity.Netcode.RuntimeTests { public class NetworkObjectOwnershipComponent : NetworkBehaviour { + public static Dictionary SpawnedInstances = new Dictionary(); + public bool OnLostOwnershipFired = false; public bool OnGainedOwnershipFired = false; @@ -23,6 +25,15 @@ public override void OnGainedOwnership() OnGainedOwnershipFired = true; } + public override void OnNetworkSpawn() + { + if (!SpawnedInstances.ContainsKey(NetworkManager.LocalClientId)) + { + SpawnedInstances.Add(NetworkManager.LocalClientId, this); + } + base.OnNetworkSpawn(); + } + public void ResetFlags() { OnLostOwnershipFired = false; @@ -30,6 +41,9 @@ public void ResetFlags() } } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkObjectOwnershipTests : NetcodeIntegrationTest @@ -48,10 +62,22 @@ public enum OwnershipChecks Remove } + protected override IEnumerator OnSetup() + { + NetworkObjectOwnershipComponent.SpawnedInstances.Clear(); + return base.OnSetup(); + } + protected override void OnServerAndClientsCreated() { m_OwnershipPrefab = CreateNetworkObjectPrefab("OnwershipPrefab"); m_OwnershipPrefab.AddComponent(); +#if NGO_DAMODE + if (m_DistributedAuthority) + { + m_OwnershipPrefab.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + } +#endif base.OnServerAndClientsCreated(); } @@ -67,6 +93,23 @@ public void TestPlayerIsOwned() Assert.NotNull(clientPlayerObject, $"Client Id {m_ClientNetworkManagers[0].LocalClientId} does not have its local player marked as an owned object using local client!"); } + private bool AllObjectsSpawnedOnClients() + { + if (!NetworkObjectOwnershipComponent.SpawnedInstances.ContainsKey(m_ServerNetworkManager.LocalClientId)) + { + return false; + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!NetworkObjectOwnershipComponent.SpawnedInstances.ContainsKey(client.LocalClientId)) + { + return false; + } + } + return true; + } + [UnityTest] public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChecks) { @@ -75,6 +118,9 @@ public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChec yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeHandled(m_ClientNetworkManagers[0]); + yield return WaitForConditionOrTimeOut(AllObjectsSpawnedOnClients); + AssertOnTimeout($"Timed out waiting for all clients to spawn the ownership object!"); + var ownershipNetworkObjectId = m_OwnershipNetworkObject.NetworkObjectId; Assert.That(ownershipNetworkObjectId, Is.GreaterThan(0)); Assert.That(m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(ownershipNetworkObjectId)); @@ -123,18 +169,26 @@ public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChec else { // Validates that when ownership is removed the server gets an OnGainedOwnership notification +#if NGO_DAMODE + // In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service) + if (m_DistributedAuthority) + { + clientObject.ChangeOwnership(NetworkManager.ServerClientId); + } + else + { + serverObject.RemoveOwnership(); + } +#else serverObject.RemoveOwnership(); +#endif } - yield return s_DefaultWaitForTick; + yield return WaitForConditionOrTimeOut(() => serverComponent.OnGainedOwnershipFired && serverComponent.OwnerClientId == m_ServerNetworkManager.LocalClientId); + AssertOnTimeout($"Timed out waiting for ownership to be transfered back to the host instance!"); - Assert.That(serverComponent.OnGainedOwnershipFired); - Assert.That(serverComponent.OwnerClientId, Is.EqualTo(m_ServerNetworkManager.LocalClientId)); - - yield return WaitForConditionOrTimeOut(() => clientComponent.OnLostOwnershipFired); - Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client to lose ownership!"); - Assert.That(clientComponent.OnLostOwnershipFired); - Assert.That(clientComponent.OwnerClientId, Is.EqualTo(m_ServerNetworkManager.LocalClientId)); + yield return WaitForConditionOrTimeOut(() => clientComponent.OnLostOwnershipFired && clientComponent.OwnerClientId == m_ServerNetworkManager.LocalClientId); + AssertOnTimeout($"Timed out waiting for client-side lose ownership event to trigger or owner identifier to be equal to the host!"); } /// @@ -193,11 +247,11 @@ bool WaitForClientsToSpawnNetworkObject() m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; Assert.That(serverObject, Is.Not.Null); - + var clientObject = (NetworkObject)null; var clientObjects = new List(); for (int i = 0; i < NumberOfClients; i++) { - var clientObject = m_ClientNetworkManagers[i].SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; + clientObject = m_ClientNetworkManagers[i].SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; Assert.That(clientObject, Is.Not.Null); clientObjects.Add(clientObject); } @@ -217,9 +271,10 @@ bool WaitForClientsToSpawnNetworkObject() // After the 1st client has been given ownership to the object, this will be used to make sure each previous owner properly received the remove ownership message var previousClientComponent = (NetworkObjectOwnershipComponent)null; + for (int clientIndex = 0; clientIndex < NumberOfClients; clientIndex++) { - var clientObject = clientObjects[clientIndex]; + clientObject = clientObjects[clientIndex]; var clientId = m_ClientNetworkManagers[clientIndex].LocalClientId; Assert.That(m_ServerNetworkManager.ConnectedClients.ContainsKey(clientId)); @@ -271,7 +326,19 @@ bool WaitForClientsToSpawnNetworkObject() else { // Validates that when ownership is removed the server gets an OnGainedOwnership notification +#if NGO_DAMODE + // In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service) + if (m_DistributedAuthority) + { + clientObject.ChangeOwnership(NetworkManager.ServerClientId); + } + else + { + serverObject.RemoveOwnership(); + } +#else serverObject.RemoveOwnership(); +#endif } yield return WaitForConditionOrTimeOut(ownershipMessageHooks); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs index 8f35c970c0..e8a0472b35 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs @@ -6,6 +6,10 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(SessionModeTypes.DistributedAuthority)] +#endif public class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; @@ -15,6 +19,9 @@ public class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest private NetworkPrefab m_PrefabToSpawn; +#if NGO_DAMODE + public NetworkObjectSpawnManyObjectsTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif // Using this component assures we will know precisely how many prefabs were spawned on the client public class SpawnObjecTrackingComponent : NetworkBehaviour { @@ -35,6 +42,7 @@ protected override void OnServerAndClientsCreated() var gameObject = new GameObject("TestObject"); var networkObject = gameObject.AddComponent(); NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + networkObject.IsSceneObject = false; gameObject.AddComponent(); m_PrefabToSpawn = new NetworkPrefab() { Prefab = gameObject }; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index 3ac712e1e3..8cd6c9fd81 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -8,6 +8,9 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.DAHost)] +#endif [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Host)] [TestFixture(VariableLengthSafety.EnabledNetVarSafety, HostOrServer.Host)] [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Server)] @@ -29,17 +32,22 @@ public enum VariableLengthSafety { DisableNetVarSafety, EnabledNetVarSafety, - } + }; - public NetworkObjectSynchronizationTests(VariableLengthSafety variableLengthSafety, HostOrServer hostOrServer) + public NetworkObjectSynchronizationTests(VariableLengthSafety variableLengthSafety, HostOrServer hostOrServer) : base(hostOrServer) { m_VariableLengthSafety = variableLengthSafety; - m_UseHost = hostOrServer == HostOrServer.Host; } protected override void OnCreatePlayerPrefab() { - m_PlayerPrefab.AddComponent(); + var component = m_PlayerPrefab.AddComponent(); +#if NGO_DAMODE + if (m_DistributedAuthority) + { + component.SetWritePermissions(NetworkVariableWritePermission.Owner); + } +#endif base.OnCreatePlayerPrefab(); } @@ -123,40 +131,70 @@ public IEnumerator NetworkObjectDeserializationFailure() if (m_UseHost) { + var delayCounter = 0; + while (m_ClientNetworkManagers.Length == 0) + { + delayCounter++; + Assert.True(delayCounter < 30, "TimeOut waiting for client to spawn!"); + yield return s_DefaultWaitForTick; + } + delayCounter = 0; + while (!m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_ClientNetworkManagers[0].LocalClientId)) + { + delayCounter++; + if (delayCounter >= 30) + { + VerboseDebug("Trap!"); + } + Assert.True(delayCounter < 30, "TimeOut waiting for client to spawn!"); + yield return s_DefaultWaitForTick; + } + + var serverSideClientPlayerComponent = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); var serverSideHostPlayerComponent = m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent(); var clientSidePlayerComponent = m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); var clientSideHostPlayerComponent = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ServerNetworkManager.LocalClientId].GetComponent(); - +#if NGO_DAMODE + var modeText = m_DistributedAuthority ? "owner" : "server"; +#else + var modeText = "server"; +#endif // Validate that the client side player values match the server side value of the client's player Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData1.Value == clientSidePlayerComponent.NetworkVariableData1.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData1.Value})" + - $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData1.Value})!"); + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Client Player-{clientSidePlayerComponent.OwnerClientId}] Client side value ({clientSidePlayerComponent.NetworkVariableData1.Value})" + + $" does not equal the {modeText} side value ({serverSideClientPlayerComponent.NetworkVariableData1.Value})!"); Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData2.Value == clientSidePlayerComponent.NetworkVariableData2.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData2.Value})" + - $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData2.Value})!"); + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Client Player-{clientSidePlayerComponent.OwnerClientId}] Client side value ({clientSidePlayerComponent.NetworkVariableData2.Value})" + + $" does not equal the {modeText} side value ({serverSideClientPlayerComponent.NetworkVariableData2.Value})!"); Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData3.Value == clientSidePlayerComponent.NetworkVariableData3.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData3.Value})" + - $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData3.Value})!"); + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Client Player-{clientSidePlayerComponent.OwnerClientId}] Client side value ({clientSidePlayerComponent.NetworkVariableData3.Value})" + + $" does not equal the {modeText} side value ({serverSideClientPlayerComponent.NetworkVariableData3.Value})!"); Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData4.Value == clientSidePlayerComponent.NetworkVariableData4.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Client Player] Client side value ({serverSideClientPlayerComponent.NetworkVariableData4.Value})" + - $" does not equal the server side value ({serverSideClientPlayerComponent.NetworkVariableData4.Value})!"); - - - // Validate that only the 2nd and 4th NetworkVariable on the client side instance of the host's player is the same and the other two do not match - // (i.e. NetworkVariables owned by the server should not get synchronized on client) - Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData1.Value != clientSideHostPlayerComponent.NetworkVariableData1.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData1.Value})" + - $" should not be equal to the server side value ({clientSideHostPlayerComponent.NetworkVariableData1.Value})!"); - Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData2.Value == clientSideHostPlayerComponent.NetworkVariableData2.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData2.Value})" + - $" does not equal the server side value ({clientSideHostPlayerComponent.NetworkVariableData2.Value})!"); - Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData3.Value != clientSideHostPlayerComponent.NetworkVariableData3.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData3.Value})" + - $" should not be equal to the server side value ({clientSideHostPlayerComponent.NetworkVariableData3.Value})!"); - Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData4.Value == clientSideHostPlayerComponent.NetworkVariableData4.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Host Player] Client side value ({serverSideHostPlayerComponent.NetworkVariableData4.Value})" + - $" does not equal the server side value ({clientSideHostPlayerComponent.NetworkVariableData4.Value})!"); + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Client Player-{clientSidePlayerComponent.OwnerClientId}] Client side value ({clientSidePlayerComponent.NetworkVariableData4.Value})" + + $" does not equal the {modeText} side value ({serverSideClientPlayerComponent.NetworkVariableData4.Value})!"); + +#if NGO_DAMODE + // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions + // to only allow the service to write. For now, we will skip this validation for distributed authority + if (!m_DistributedAuthority) +#endif + { + // Validate that only the 2nd and 4th NetworkVariable on the client side instance of the host's player is the same and the other two do not match + // (i.e. NetworkVariables owned by the server should not get synchronized on client) + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData1.Value != clientSideHostPlayerComponent.NetworkVariableData1.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Host Player] Client side value ({clientSideHostPlayerComponent.NetworkVariableData1.Value})" + + $" should not be equal to the server side value ({serverSideHostPlayerComponent.NetworkVariableData1.Value})!"); + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData2.Value == clientSideHostPlayerComponent.NetworkVariableData2.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Host Player] Client side value ({clientSideHostPlayerComponent.NetworkVariableData2.Value})" + + $" does not equal the server side value ({serverSideHostPlayerComponent.NetworkVariableData2.Value})!"); + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData3.Value != clientSideHostPlayerComponent.NetworkVariableData3.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Host Player] Client side value ({clientSideHostPlayerComponent.NetworkVariableData3.Value})" + + $" should not be equal to the server side value ({serverSideHostPlayerComponent.NetworkVariableData3.Value})!"); + Assert.IsTrue(serverSideHostPlayerComponent.NetworkVariableData4.Value == serverSideHostPlayerComponent.NetworkVariableData4.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Host Player] Client side value ({clientSideHostPlayerComponent.NetworkVariableData4.Value})" + + $" does not equal the server side value ({serverSideHostPlayerComponent.NetworkVariableData4.Value})!"); + } } else { @@ -190,44 +228,57 @@ public IEnumerator NetworkObjectDeserializationFailure() $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Player-{clientOneId}] Client-{clientOneId} value ({clientSide1PlayerComponent.NetworkVariableData4.Value})" + $" does not equal Client-{clientTwoId}'s clone side value ({clientSide2Player1Clone.NetworkVariableData4.Value})!"); - - // Validate that client two's 2nd and 4th NetworkVariables for the local and clone instances match and the other two do not - Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData1.Value != clientSide1Player2Clone.NetworkVariableData1.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData1.Value})" + - $" should not be equal to Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData1.Value})!"); - - Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData2.Value == clientSide1Player2Clone.NetworkVariableData2.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData2.Value})" + - $" does not equal Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData2.Value})!"); - - Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData3.Value != clientSide1Player2Clone.NetworkVariableData3.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData3.Value})" + - $" should not be equal to Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData3.Value})!"); - - Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData4.Value == clientSide1Player2Clone.NetworkVariableData4.Value, - $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData4.Value})" + - $" does not equal Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData4.Value})!"); +#if NGO_DAMODE + // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions + // to only allow the service to write. For now, we will skip this validation for distributed authority + if (!m_DistributedAuthority) +#endif + { + // Validate that client two's 2nd and 4th NetworkVariables for the local and clone instances match and the other two do not + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData1.Value != clientSide1Player2Clone.NetworkVariableData1.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData1.Value})" + + $" should not be equal to Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData1.Value})!"); + + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData2.Value == clientSide1Player2Clone.NetworkVariableData2.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData2)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData2.Value})" + + $" does not equal Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData2.Value})!"); + + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData3.Value != clientSide1Player2Clone.NetworkVariableData3.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData3)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData3.Value})" + + $" should not be equal to Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData3.Value})!"); + + Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData4.Value == clientSide1Player2Clone.NetworkVariableData4.Value, + $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Player-{clientTwoId}] Client-{clientTwoId} value ({clientSide2PlayerComponent.NetworkVariableData4.Value})" + + $" does not equal Client-{clientOneId}'s clone side value ({clientSide1Player2Clone.NetworkVariableData4.Value})!"); + } } - // Now validate all of the NetworkVariable values match to assure everything synchronized properly - foreach (var spawnedObject in validSpawnedNetworkObjects) +#if NGO_DAMODE + // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions + // to only allow the service to write. For now, we will skip this validation for distributed authority + if (!m_DistributedAuthority) +#endif { - foreach (var clientNetworkManager in m_ClientNetworkManagers) + // Now validate all of the NetworkVariable values match to assure everything synchronized properly + foreach (var spawnedObject in validSpawnedNetworkObjects) { - //Validate that the connected client has spawned all of the instances that shouldn't have failed. - var clientSideNetworkObjects = s_GlobalNetworkObjects[clientNetworkManager.LocalClientId]; + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + //Validate that the connected client has spawned all of the instances that shouldn't have failed. + var clientSideNetworkObjects = s_GlobalNetworkObjects[clientNetworkManager.LocalClientId]; - Assert.IsTrue(NetworkBehaviourWithNetworkVariables.ClientSpawnCount[clientNetworkManager.LocalClientId] == validSpawnedNetworkObjects.Count, $"Client-{clientNetworkManager.LocalClientId} spawned " + - $"({NetworkBehaviourWithNetworkVariables.ClientSpawnCount}) {nameof(NetworkObject)}s but the expected number of {nameof(NetworkObject)}s should have been ({validSpawnedNetworkObjects.Count})!"); + Assert.IsTrue(NetworkBehaviourWithNetworkVariables.ClientSpawnCount[clientNetworkManager.LocalClientId] == validSpawnedNetworkObjects.Count, $"Client-{clientNetworkManager.LocalClientId} spawned " + + $"({NetworkBehaviourWithNetworkVariables.ClientSpawnCount}) {nameof(NetworkObject)}s but the expected number of {nameof(NetworkObject)}s should have been ({validSpawnedNetworkObjects.Count})!"); - var spawnedNetworkObject = spawnedObject.GetComponent(); - Assert.IsTrue(clientSideNetworkObjects.ContainsKey(spawnedNetworkObject.NetworkObjectId), $"Failed to find valid spawned {nameof(NetworkObject)} on the client-side with a " + - $"{nameof(NetworkObject.NetworkObjectId)} of {spawnedNetworkObject.NetworkObjectId}"); + var spawnedNetworkObject = spawnedObject.GetComponent(); + Assert.IsTrue(clientSideNetworkObjects.ContainsKey(spawnedNetworkObject.NetworkObjectId), $"Failed to find valid spawned {nameof(NetworkObject)} on the client-side with a " + + $"{nameof(NetworkObject.NetworkObjectId)} of {spawnedNetworkObject.NetworkObjectId}"); - var clientSideObject = clientSideNetworkObjects[spawnedNetworkObject.NetworkObjectId]; - Assert.IsTrue(clientSideObject.NetworkManager == clientNetworkManager, $"Client-side object {clientSideObject}'s {nameof(NetworkManager)} is not valid!"); + var clientSideObject = clientSideNetworkObjects[spawnedNetworkObject.NetworkObjectId]; + Assert.IsTrue(clientSideObject.NetworkManager == clientNetworkManager, $"Client-side object {clientSideObject}'s {nameof(NetworkManager)} is not valid!"); - ValidateNetworkBehaviourWithNetworkVariables(spawnedNetworkObject, clientSideObject); + ValidateNetworkBehaviourWithNetworkVariables(spawnedNetworkObject, clientSideObject); + } } } } @@ -259,6 +310,22 @@ private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject serverSi $"does not match the client side instance value ({clientSideComponent.NetworkVariableData4.Value})!"); } + + private bool ClientSpawnedNetworkObjects(List spawnedObjectList) + { + var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; + + foreach (var spawnedObject in spawnedObjectList) + { + var serverSideSpawnedNetworkObject = spawnedObject.GetComponent(); + if (!clientSideNetworkObjects.ContainsKey(serverSideSpawnedNetworkObject.NetworkObjectId)) + { + return false; + } + } + return true; + } + /// /// This validates that when a NetworkBehaviour fails serialization or deserialization during synchronizations that other NetworkBehaviours /// will still be initialized properly @@ -287,6 +354,9 @@ public IEnumerator NetworkBehaviourSynchronization() // Validate that when a NetworkBehaviour fails to synchronize and is skipped over it does not // impact the rest of the NetworkBehaviours. var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; + yield return WaitForConditionOrTimeOut(() => ClientSpawnedNetworkObjects(spawnedObjectList)); + AssertOnTimeout($"Timed out waiting for newly joined client to spawn all NetworkObjects!"); + foreach (var spawnedObject in spawnedObjectList) { var serverSideSpawnedNetworkObject = spawnedObject.GetComponent(); @@ -387,6 +457,27 @@ public override void OnNetworkSpawn() /// public class NetworkBehaviourWithOwnerNetworkVariables : NetworkBehaviour { +#if NGO_DAMODE + private NetworkVariableWritePermission m_NetworkVariableWritePermission = NetworkVariableWritePermission.Server; + /// + /// For distributed authority, there is no such thing as a server and only owners + /// DANGO-TODO: When NetworkVariable permissions are updated, this test might need to be updated + /// + /// + public void SetWritePermissions(NetworkVariableWritePermission networkVariableWritePermission) + { + m_NetworkVariableWritePermission = networkVariableWritePermission; + // Should synchronize with everyone + NetworkVariableData1 = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, networkVariableWritePermission); + // Should synchronize with everyone + NetworkVariableData2 = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, networkVariableWritePermission); + // Should synchronize with everyone + NetworkVariableData3 = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, networkVariableWritePermission); + // Should synchronize with everyone + NetworkVariableData4 = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, networkVariableWritePermission); + } +#endif + // Should not synchronize on non-owners public NetworkVariable NetworkVariableData1 = new NetworkVariable(default, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Server); @@ -399,7 +490,14 @@ public class NetworkBehaviourWithOwnerNetworkVariables : NetworkBehaviour public override void OnNetworkSpawn() { + // Adjustment for distributed authority mode +#if NGO_DAMODE + if ((m_NetworkVariableWritePermission == NetworkVariableWritePermission.Server && IsServer && !NetworkManager.DistributedAuthorityMode) || + (m_NetworkVariableWritePermission == NetworkVariableWritePermission.Owner && IsOwner && NetworkManager.DistributedAuthorityMode)) +#else if (IsServer) +#endif + { NetworkVariableData1.Value = Random.Range(1, 1000); NetworkVariableData2.Value = Random.Range(1, 1000); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index a68a0412dd..bb0413b8da 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -16,7 +17,6 @@ public class ShowHideObject : NetworkBehaviour public static int ValueAfterOwnershipChange = 0; public static Dictionary ObjectsPerClientId = new Dictionary(); public static List ClientIdsRpcCalledOn; - public static NetworkObject GetNetworkObjectById(ulong networkObjectId) { foreach (var entry in ClientTargetedNetworkObjects) @@ -113,7 +113,10 @@ public void Changed(int before, int after) [ClientRpc] public void SomeRandomClientRPC() { - Debug.Log($"RPC called {NetworkManager.LocalClientId}"); + if (!Silent) + { + Debug.Log($"RPC called {NetworkManager.LocalClientId}"); + } ClientIdsRpcCalledOn?.Add(NetworkManager.LocalClientId); } @@ -123,12 +126,17 @@ public void TriggerRpc() } } +#if NGO_DAMODE + [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(SessionModeTypes.DistributedAuthority)] +#endif public class NetworkShowHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; private ulong m_ClientId0; private GameObject m_PrefabToSpawn; + private GameObject m_PrefabSpawnWithoutObservers; private NetworkObject m_NetSpawnedObject1; private NetworkObject m_NetSpawnedObject2; @@ -137,10 +145,16 @@ public class NetworkShowHideTests : NetcodeIntegrationTest private NetworkObject m_Object2OnClient0; private NetworkObject m_Object3OnClient0; +#if NGO_DAMODE + public NetworkShowHideTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("ShowHideObject"); m_PrefabToSpawn.AddComponent(); + + m_PrefabSpawnWithoutObservers = CreateNetworkObjectPrefab("ObserversObject"); + m_PrefabSpawnWithoutObservers.GetComponent().SpawnWithObservers = false; } // Check that the first client see them, or not, as expected @@ -377,6 +391,107 @@ public IEnumerator NetworkHideDespawnTest() LogAssert.NoUnexpectedReceived(); } + + private List m_ClientsWithVisibility = new List(); + private NetworkObject m_ObserverTestObject; + + 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) + { + if (m_ClientsWithVisibility.Contains(client.LocalClientId)) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(m_ObserverTestObject.NetworkObjectId)) + { + return false; + } + } + } + return true; + } + + [UnityTest] + public IEnumerator SpawnWithoutObserversTest() + { + var spawnedObject = SpawnObject(m_PrefabSpawnWithoutObservers, m_ServerNetworkManager); + + m_ObserverTestObject = spawnedObject.GetComponent(); + + yield return WaitForTicks(m_ServerNetworkManager, 3); + +#if NGO_DAMODE + // 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 + if (!m_DistributedAuthority) +#endif + { + // 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_ClientsWithVisibility.Add(m_ServerNetworkManager.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}!"); + + yield return WaitForConditionOrTimeOut(CheckListedClientsVisibility); + AssertOnTimeout($"[Authority-Only] Timed out waiting for only the authority to be an observer!"); + + foreach (var client in m_ClientNetworkManagers) + { + 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!"); + 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}!"); + + yield return WaitForConditionOrTimeOut(CheckListedClientsVisibility); + AssertOnTimeout($"[Client-{client.LocalClientId}] Timed out waiting for the client to be an observer and spawn the {nameof(NetworkObject)}!"); + Assert.False(client.SpawnManager.SpawnedObjects[m_ObserverTestObject.NetworkObjectId].SpawnWithObservers, $"Client-{client.LocalClientId} instance of {m_ObserverTestObject.name} has a {nameof(NetworkObject.SpawnWithObservers)} value of true!"); + } + } + + private bool ClientsSpawnedObject1() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId)) + { + return false; + } + } + return true; + } + + private StringBuilder m_ErrorLog = new StringBuilder(); + private ulong m_ClientWithoutVisibility; + private bool Object1IsNotVisibileToClient() + { + m_ErrorLog.Clear(); + foreach (var client in m_ClientNetworkManagers) + { + if (client.LocalClientId == m_ClientWithoutVisibility) + { + if (client.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"{m_NetSpawnedObject1.name} is still visible to Client-{m_ClientWithoutVisibility}!"); + } + } + else + if (client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId].IsNetworkVisibleTo(m_ClientWithoutVisibility)) + { + m_ErrorLog.AppendLine($"Local instance of {m_NetSpawnedObject1.name} on Client-{client.LocalClientId} thinks Client-{m_ClientWithoutVisibility} still has visibility!"); + } + } + return m_ErrorLog.Length == 0; + } + [UnityTest] public IEnumerator NetworkHideChangeOwnership() { @@ -387,12 +502,29 @@ public IEnumerator NetworkHideChangeOwnership() var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); + yield return WaitForConditionOrTimeOut(ClientsSpawnedObject1); + AssertOnTimeout($"Not all clients spawned object!"); + m_NetSpawnedObject1.GetComponent().MyNetworkVariable.Value++; // Hide an object to a client m_NetSpawnedObject1.NetworkHide(m_ClientNetworkManagers[1].LocalClientId); + m_ClientWithoutVisibility = m_ClientNetworkManagers[1].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}"); yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 0); + foreach (var client in m_ClientNetworkManagers) + { + if (m_ClientNetworkManagers[1].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}!"); + } + // Change ownership while the object is hidden to some m_NetSpawnedObject1.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); @@ -400,30 +532,53 @@ public IEnumerator NetworkHideChangeOwnership() yield return new WaitForSeconds(1.25f); LogAssert.NoUnexpectedReceived(); - +#if NGO_DAMODE + 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); + } + else + { + m_NetSpawnedObject1.NetworkShow(m_ClientNetworkManagers[1].LocalClientId); + } +#else // Show the object again to check nothing unexpected happens m_NetSpawnedObject1.NetworkShow(m_ClientNetworkManagers[1].LocalClientId); +#endif yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 1); Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); } + private bool AllClientsSpawnedObject1() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!ShowHideObject.ObjectsPerClientId.ContainsKey(client.LocalClientId)) + { + return false; + } + } + return true; + } + [UnityTest] public IEnumerator NetworkHideChangeOwnershipNotHidden() { ShowHideObject.ClientTargetedNetworkObjects.Clear(); + ShowHideObject.ObjectsPerClientId.Clear(); ShowHideObject.ClientIdToTarget = m_ClientNetworkManagers[1].LocalClientId; ShowHideObject.Silent = true; var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); 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(AllClientsSpawnedObject1); + AssertOnTimeout($"Timed out waiting for all clients to spawn {spawnedObject1.name}!"); // change the value m_NetSpawnedObject1.GetComponent().MyOwnerReadNetworkVariable.Value++; @@ -445,10 +600,10 @@ public IEnumerator NetworkHideChangeOwnershipNotHidden() yield return WaitForTicks(m_ClientNetworkManagers[0], 3); // verify ownership changed - Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); + Assert.AreEqual(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId, m_ClientNetworkManagers[0].LocalClientId); // verify the expected client got the OnValueChanged. (Only client 1 sets this value) - Assert.True(ShowHideObject.ValueAfterOwnershipChange == 1); + Assert.AreEqual(1, ShowHideObject.ValueAfterOwnershipChange); } private string Display(NetworkList list) @@ -487,20 +642,20 @@ private void Compare(NetworkList list1, NetworkList list2) private IEnumerator HideThenShowAndHideThenModifyAndShow() { - Debug.Log("Hiding"); + VerboseDebug("Hiding"); // hide m_NetSpawnedObject1.NetworkHide(1); yield return WaitForTicks(m_ServerNetworkManager, 3); yield return WaitForTicks(m_ClientNetworkManagers[0], 3); - Debug.Log("Showing and Hiding"); + 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); - Debug.Log("Modifying and Showing"); + VerboseDebug("Modifying and Showing"); // modify and show m_NetSpawnedObject1.GetComponent().MyList.Add(5); m_NetSpawnedObject1.NetworkShow(1); @@ -575,19 +730,19 @@ public IEnumerator NetworkShowHideAroundListModify() switch (i) { case 0: - Debug.Log("Running HideThenModifyAndShow"); + VerboseDebug("Running HideThenModifyAndShow"); yield return HideThenModifyAndShow(); break; case 1: - Debug.Log("Running HideThenShowAndModify"); + VerboseDebug("Running HideThenShowAndModify"); yield return HideThenShowAndModify(); break; case 2: - Debug.Log("Running HideThenShowAndHideThenModifyAndShow"); + VerboseDebug("Running HideThenShowAndHideThenModifyAndShow"); yield return HideThenShowAndHideThenModifyAndShow(); break; case 3: - Debug.Log("Running HideThenShowAndRPC"); + VerboseDebug("Running HideThenShowAndRPC"); ShowHideObject.ClientIdsRpcCalledOn = new List(); yield return HideThenShowAndRPC(); // Provide enough time for slower systems or VM systems possibly under a heavy load could fail on this test diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs index 0806775a16..5bbcb1edcd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs @@ -1,11 +1,14 @@ using System.Collections; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; -using UnityEngine; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif + [TestFixture(HostOrServer.Host)] public class NetworkSpawnManagerTests : NetcodeIntegrationTest { private ulong serverSideClientId => NetworkManager.ServerClientId; @@ -14,6 +17,8 @@ public class NetworkSpawnManagerTests : NetcodeIntegrationTest protected override int NumberOfClients => 2; + public NetworkSpawnManagerTests(HostOrServer hostOrServer) : base(hostOrServer) { } + [Test] public void TestServerCanAccessItsOwnPlayer() { @@ -23,9 +28,16 @@ public void TestServerCanAccessItsOwnPlayer() Assert.AreEqual(serverSideClientId, serverSideServerPlayerObject.OwnerClientId); } - [Test] - public void TestServerCanAccessOtherPlayers() + + /// + /// Test was converted from a Test to UnityTest so distributed authority mode will pass this test. + /// In distributed authority mode, client-side player spawning is enabled by default which requires + /// all client (including DAHost) instances to wait for all players to be spawned. + /// + [UnityTest] + public IEnumerator TestServerCanAccessOtherPlayers() { + yield return null; // server can access other players var serverSideClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(clientSideClientId); Assert.NotNull(serverSideClientPlayerObject); @@ -34,11 +46,19 @@ public void TestServerCanAccessOtherPlayers() var serverSideOtherClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(otherClientSideClientId); Assert.NotNull(serverSideOtherClientPlayerObject); Assert.AreEqual(otherClientSideClientId, serverSideOtherClientPlayerObject.OwnerClientId); + } [Test] public void TestClientCantAccessServerPlayer() { +#if NGO_DAMODE + if (m_DistributedAuthority) + { + VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_SessionModeType} mode."); + return; + } +#endif // client can't access server player Assert.Throws(() => { @@ -54,10 +74,32 @@ public void TestClientCanAccessOwnPlayer() Assert.NotNull(clientSideClientPlayerObject); Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId); } +#if NGO_DAMODE + [Test] + public void TestClientCanAccessOtherPlayer() + { + + if (!m_DistributedAuthority) + { + VerboseDebug($"Ignoring test: Clients do not have access to other player objects in {m_SessionModeType} mode."); + return; + } + + var otherClientPlayer = m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(otherClientSideClientId); + Assert.NotNull(otherClientPlayer, $"Failed to obtain Client{otherClientSideClientId}'s player object!"); + } +#endif [Test] public void TestClientCantAccessOtherPlayer() { +#if NGO_DAMODE + if (m_DistributedAuthority) + { + VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_SessionModeType} mode."); + return; + } +#endif // client can't access other player Assert.Throws(() => { @@ -97,18 +139,8 @@ public void TestClientCanUseGetLocalPlayerObject() public IEnumerator TestConnectAndDisconnect() { // test when client connects, player object is now available - - // connect new client - if (!NetcodeIntegrationTestHelpers.CreateNewClients(1, out NetworkManager[] clients)) - { - Debug.LogError("Failed to create instances"); - Assert.Fail("Failed to create instances"); - } - var newClientNetworkManager = clients[0]; - newClientNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; - newClientNetworkManager.StartClient(); - yield return NetcodeIntegrationTestHelpers.WaitForClientConnected(newClientNetworkManager); - yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.ConnectedClients.ContainsKey(newClientNetworkManager.LocalClientId)); + yield return CreateAndStartNewClient(); + var newClientNetworkManager = m_ClientNetworkManagers[NumberOfClients]; var newClientLocalClientId = newClientNetworkManager.LocalClientId; // test new client can get that itself locally diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 736d40268b..9d725e0be8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -12,12 +12,12 @@ namespace Unity.Netcode.RuntimeTests public class NetworkTransformBase : IntegrationTestWithApproximation { - // The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime + // The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime protected const int k_PositionRotationScaleIterations = 3; protected const int k_PositionRotationScaleIterations3Axis = 8; protected float m_CurrentHalfPrecision = 0.0f; - protected const float k_HalfPrecisionPosScale = 0.115f; + protected const float k_HalfPrecisionPosScale = 0.1256f; protected const float k_HalfPrecisionRot = 0.725f; @@ -126,7 +126,7 @@ protected override float GetDeltaVarianceThreshold() { return m_CurrentHalfPrecision; } - return 0.045f; + return 0.055f; } /// @@ -473,12 +473,12 @@ protected bool PostAllChildrenLocalTransformValuesMatch(bool useSubChild) } if (!Approximately(childLocalPosition, authorityObjectLocalPosition)) { - m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Position ({childLocalPosition}) | Authority Local Position ({authorityObjectLocalPosition})"); + m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Position ({GetVector3Values(childLocalPosition)}) | Authority Local Position ({GetVector3Values(authorityObjectLocalPosition)})"); success = false; } if (!Approximately(childLocalScale, authorityObjectLocalScale)) { - m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Scale ({childLocalScale}) | Authority Local Scale ({authorityObjectLocalScale})"); + m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Scale ({GetVector3Values(childLocalScale)}) | Authority Local Scale ({GetVector3Values(authorityObjectLocalScale)})"); success = false; } @@ -489,7 +489,7 @@ protected bool PostAllChildrenLocalTransformValuesMatch(bool useSubChild) } if (!ApproximatelyEuler(childLocalRotation, authorityObjectLocalRotation)) { - m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Rotation ({childLocalRotation}) | Authority Local Rotation ({authorityObjectLocalRotation})"); + m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Rotation ({GetVector3Values(childLocalRotation)}) | Authority Local Rotation ({GetVector3Values(authorityObjectLocalRotation)})"); success = false; } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 1bcd7a9c0a..79b9bec65d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -9,12 +9,38 @@ using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost, MotionModels.UseTransform)] + [TestFixture(HostOrServer.DAHost, MotionModels.UseRigidbody)] + [TestFixture(HostOrServer.Host, MotionModels.UseTransform)] +#else + [TestFixture(HostOrServer.Host)] +#endif public class NetworkTransformOwnershipTests : IntegrationTestWithApproximation { +#if NGO_DAMODE + public enum MotionModels + { + UseRigidbody, + UseTransform + } +#endif protected override int NumberOfClients => 1; private GameObject m_ClientNetworkTransformPrefab; private GameObject m_NetworkTransformPrefab; +#if NGO_DAMODE + private MotionModels m_MotionModel; +#endif + +#if NGO_DAMODE + public NetworkTransformOwnershipTests(HostOrServer hostOrServer, MotionModels motionModel) : base(hostOrServer) + { + m_MotionModel = motionModel; + } +#else + public NetworkTransformOwnershipTests(HostOrServer hostOrServer) : base(hostOrServer) {} +#endif protected override void OnServerAndClientsCreated() { @@ -28,7 +54,10 @@ protected override void OnServerAndClientsCreated() // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results - m_ClientNetworkTransformPrefab.AddComponent(); + var networkRigidbody = m_ClientNetworkTransformPrefab.AddComponent(); +#if NGO_DAMODE + networkRigidbody.UseRigidBodyForMotion = m_MotionModel == MotionModels.UseRigidbody; +#endif m_ClientNetworkTransformPrefab.AddComponent(); m_NetworkTransformPrefab = CreateNetworkObjectPrefab("ServerAuthorityTest"); @@ -38,7 +67,10 @@ protected override void OnServerAndClientsCreated() // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results - m_NetworkTransformPrefab.AddComponent(); + networkRigidbody = m_NetworkTransformPrefab.AddComponent(); +#if NGO_DAMODE + networkRigidbody.UseRigidBodyForMotion = m_MotionModel == MotionModels.UseRigidbody; +#endif m_NetworkTransformPrefab.AddComponent(); networkTransform.Interpolate = false; networkTransform.UseHalfFloatPrecision = false; @@ -99,10 +131,14 @@ public IEnumerator LateJoinedNonOwnerClientCannotChangeTransform() // Wait until the client gains ownership yield return WaitForConditionOrTimeOut(ClientIsOwner); + AssertOnTimeout($"Timed out waiting for the {nameof(ClientIsOwner)} condition to be met!"); // Spawn a new client yield return CreateAndStartNewClient(); + yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ClientNetworkManagers[1].LocalClientId)); + AssertOnTimeout($"Timed out waiting for late joing client VerifyObjectIsSpawnedOnClient entry to be created!"); + // Get the instance of the object relative to the newly joined client var newClientObjectInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ClientNetworkManagers[1].LocalClientId); @@ -117,7 +153,19 @@ public IEnumerator LateJoinedNonOwnerClientCannotChangeTransform() // Wait one frame so the NetworkTransform can apply the owner's last state received on the late joining client side // (i.e. prevent the non-owner from changing the transform) - yield return null; + +#if NGO_DAMODE + if (m_MotionModel == MotionModels.UseRigidbody) + { + // Allow fixed update to run twice for values to propogate to Unity transform + yield return new WaitForFixedUpdate(); + yield return new WaitForFixedUpdate(); + } + else +#endif + { + yield return null; + } // Get the owner instance var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ClientNetworkManagers[0].LocalClientId); @@ -139,6 +187,23 @@ public enum StartingOwnership ClientStartsAsOwner, } + private bool ClientAndServerSpawnedInstance() + { + return VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClientId) && VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClientId); + } + + private bool m_UseAdjustedVariance; + private const float k_AdjustedVariance = 0.025f; + + protected override float GetDeltaVarianceThreshold() + { + if (m_UseAdjustedVariance) + { + return k_AdjustedVariance; + } + return base.GetDeltaVarianceThreshold(); + } + /// /// This verifies that when authority is owner authoritative the owner's /// Rigidbody is kinematic and the non-owner's is not. @@ -155,7 +220,8 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn // Spawn the m_ClientNetworkTransformPrefab and wait for the client-side to spawn the object var serverSideInstance = SpawnObject(m_ClientNetworkTransformPrefab, networkManagerOwner); - yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientsThatSpawnedThisPrefab().Contains(m_ClientNetworkManagers[0].LocalClientId)); + yield return WaitForConditionOrTimeOut(ClientAndServerSpawnedInstance); + AssertOnTimeout($"Timed out waiting for all object instances to be spawned!"); // Get owner relative instances var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerOwner.LocalClientId); @@ -185,12 +251,25 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn // Verify non-owners cannot change transform values nonOwnerInstance.transform.position = Vector3.zero; + yield return s_DefaultWaitForTick; + Assert.True(Approximately(nonOwnerInstance.transform.position, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{nonOwnerInstance.transform.position}"); // Change ownership and wait for the non-owner to reflect the change VerifyObjectIsSpawnedOnClient.ResetObjectTable(); +#if NGO_DAMODE + if (m_DistributedAuthority) + { + ownerInstance.NetworkObject.ChangeOwnership(networkManagerNonOwner.LocalClientId); + } + else + { + m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent(), networkManagerNonOwner.LocalClientId, true); + } +#else m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent(), networkManagerNonOwner.LocalClientId); +#endif yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent().OwnerClientId == networkManagerNonOwner.LocalClientId); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!"); @@ -206,14 +285,41 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn // Make sure the owner is not kinematic and the non-owner(s) are kinematic Assert.False(ownerInstance.GetComponent().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!"); Assert.True(nonOwnerInstance.GetComponent().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!"); + transformToTest = nonOwnerInstance.transform; + + yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation)); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + + $"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}"); + // Have the new owner change transform values and wait for those values to be applied on the non-owner side. valueSetByOwner = Vector3.one * 10; - ownerInstance.transform.position = valueSetByOwner; ownerInstance.transform.localScale = valueSetByOwner; rotation.eulerAngles = valueSetByOwner; - ownerInstance.transform.rotation = rotation; - transformToTest = nonOwnerInstance.transform; +#if NGO_DAMODE + // Allow scale to update first when using rigid body motion + if (m_MotionModel == MotionModels.UseRigidbody) + { + yield return s_DefaultWaitForTick; + yield return new WaitForFixedUpdate(); + yield return new WaitForFixedUpdate(); + } + if (m_MotionModel == MotionModels.UseRigidbody) + { + m_UseAdjustedVariance = true; + var rigidBody = ownerInstance.GetComponent(); + rigidBody.Move(valueSetByOwner, rotation); + } + else +#endif + { + m_UseAdjustedVariance = false; + ownerInstance.transform.position = valueSetByOwner; + ownerInstance.transform.rotation = rotation; + } + yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + @@ -223,7 +329,7 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn // The last check is to verify non-owners cannot change transform values after ownership has changed nonOwnerInstance.transform.position = Vector3.zero; yield return s_DefaultWaitForTick; - Assert.True(Approximately(nonOwnerInstance.transform.position, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}"); + Assert.True(Approximately(nonOwnerInstance.transform.position, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{nonOwnerInstance.transform.position}"); } /// @@ -273,59 +379,59 @@ public IEnumerator ServerAuthoritativeTest() /// public class VerifyObjectIsSpawnedOnClient : NetworkBehaviour { - private static Dictionary s_NetworkManagerRelativeSpawnedObjects = new Dictionary(); + public static Dictionary NetworkManagerRelativeSpawnedObjects = new Dictionary(); public static void ResetObjectTable() { - s_NetworkManagerRelativeSpawnedObjects.Clear(); + NetworkManagerRelativeSpawnedObjects.Clear(); } public override void OnGainedOwnership() { - if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) + if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) { - s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this); + NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this); } base.OnGainedOwnership(); } public override void OnLostOwnership() { - if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) + if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) { - s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this); + NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this); } base.OnLostOwnership(); } public static List GetClientsThatSpawnedThisPrefab() { - return s_NetworkManagerRelativeSpawnedObjects.Keys.ToList(); + return NetworkManagerRelativeSpawnedObjects.Keys.ToList(); } public static VerifyObjectIsSpawnedOnClient GetClientInstance(ulong clientId) { - if (s_NetworkManagerRelativeSpawnedObjects.ContainsKey(clientId)) + if (NetworkManagerRelativeSpawnedObjects.ContainsKey(clientId)) { - return s_NetworkManagerRelativeSpawnedObjects[clientId]; + return NetworkManagerRelativeSpawnedObjects[clientId]; } return null; } public override void OnNetworkSpawn() { - if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) + if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) { - s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this); + NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this); } base.OnNetworkSpawn(); } public override void OnNetworkDespawn() { - if (s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) + if (NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) { - s_NetworkManagerRelativeSpawnedObjects.Remove(NetworkManager.LocalClientId); + NetworkManagerRelativeSpawnedObjects.Remove(NetworkManager.LocalClientId); } base.OnNetworkDespawn(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs index 739fa4f328..bb8d103868 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs @@ -14,6 +14,14 @@ namespace Unity.Netcode.RuntimeTests /// models for each operating mode when packet loss and latency is /// present. /// +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] +#endif [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] @@ -54,13 +62,18 @@ private IEnumerator AllChildrenLocalTransformValuesMatch(bool useSubChild, Child // We don't assert on timeout here because we want to log this information during PostAllChildrenLocalTransformValuesMatch yield return WaitForConditionOrTimeOut(() => AllInstancesKeptLocalTransformValues(useSubChild)); var success = true; - m_InfoMessage.AppendLine($"[{checkType}][{useSubChild}] Timed out waiting for all children to have the correct local space values:\n"); if (s_GlobalTimeoutHelper.TimedOut) { - var waitForMs = new WaitForSeconds(0.001f); + var waitForMs = new WaitForSeconds(0.0025f); + if (m_Precision == Precision.Half) + { + m_CurrentHalfPrecision = 0.2156f; + } // If we timed out, then wait for a full range of ticks to assure all data has been synchronized before declaring this a failed test. for (int j = 0; j < m_ServerNetworkManager.NetworkConfig.TickRate; j++) { + m_InfoMessage.Clear(); + m_InfoMessage.AppendLine($"[{checkType}][{useSubChild}] Timed out waiting for all children to have the correct local space values:\n"); var instances = useSubChild ? ChildObjectComponent.SubInstances : ChildObjectComponent.Instances; success = PostAllChildrenLocalTransformValuesMatch(useSubChild); yield return waitForMs; @@ -100,6 +113,11 @@ public IEnumerator ParentedNetworkTransformTest([Values] Interpolation interpola var serverSideChild = SpawnObject(m_ChildObject.gameObject, authorityNetworkManager).GetComponent(); var serverSideSubChild = SpawnObject(m_SubChildObject.gameObject, authorityNetworkManager).GetComponent(); + yield return s_DefaultWaitForTick; + yield return s_DefaultWaitForTick; + yield return s_DefaultWaitForTick; + yield return s_DefaultWaitForTick; + // Assure all of the child object instances are spawned before proceeding to parenting yield return WaitForConditionOrTimeOut(AllChildObjectInstancesAreSpawned); AssertOnTimeout("Timed out waiting for all child instances to be spawned!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index f2512ae3c2..14bdcc7c4a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -233,6 +233,8 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu var manager = new GameObject($"Test-{nameof(NetworkManager)}.{nameof(TestSyncAxes)}"); var networkManager = manager.AddComponent(); + networkManager.NetworkConfig = new NetworkConfig(); + networkObject.NetworkManagerOwner = networkManager; networkTransform.enabled = false; // do not tick `FixedUpdate()` or `Update()` diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index bf72bba38e..7013444d94 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -9,6 +9,14 @@ namespace Unity.Netcode.RuntimeTests /// server and host operating modes and will test both authoritative /// models for each operating mode. /// +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] +#endif [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] @@ -27,7 +35,7 @@ public class NetworkTransformTests : NetworkTransformBase /// /// Constructor /// - /// Determines if we are running as a server or host + /// Determines if we are running as a server or host /// Determines if we are using server or owner authority public NetworkTransformTests(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) : base(testWithHost, authority, rotationCompression, rotation, precision) @@ -154,6 +162,10 @@ public void ParentedNetworkTransformTest([Values] Interpolation interpolation, [ Assert.True(success, "All transform values did not match prior to parenting!"); + success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches); + + Assert.True(success, "All transform values did not match prior to parenting!"); + // Parent the child under the parent with the current world position stays setting Assert.True(serverSideChild.TrySetParent(serverSideParent.transform, worldPositionStays), "[Server-Side Child] Failed to set child's parent!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs index 272e6444b7..f908b0fa92 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs @@ -6,6 +6,10 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif + [TestFixture(HostOrServer.Host)] public class NetworkVarBufferCopyTest : NetcodeIntegrationTest { public class DummyNetVar : NetworkVariableBase @@ -67,23 +71,53 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) DeltaRead = true; } + + public DummyNetVar( + NetworkVariableReadPermission readPerm = DefaultReadPerm, + NetworkVariableWritePermission writePerm = DefaultWritePerm) : base(readPerm, writePerm) { } } public class DummyNetBehaviour : NetworkBehaviour { - public DummyNetVar NetVar = new DummyNetVar(); +#if NGO_DAMODE + public static bool DistributedAuthority; +#endif + public DummyNetVar NetVar; + + private void Awake() + { +#if NGO_DAMODE + if (DistributedAuthority) + { + NetVar = new DummyNetVar(writePerm: NetworkVariableWritePermission.Owner); + } + else +#endif + { + NetVar = new DummyNetVar(); + } + } public override void OnNetworkSpawn() { +#if NGO_DAMODE + if ((NetworkManager.DistributedAuthorityMode && !IsOwner) || (!NetworkManager.DistributedAuthorityMode && !IsServer)) + { + ClientDummyNetBehaviourSpawned(this); + } +#else if (!IsServer) { ClientDummyNetBehaviourSpawned(this); } +#endif base.OnNetworkSpawn(); } } protected override int NumberOfClients => 1; + public NetworkVarBufferCopyTest(HostOrServer hostOrServer) : base(hostOrServer) { } + private static List s_ClientDummyNetBehavioursSpawned = new List(); public static void ClientDummyNetBehaviourSpawned(DummyNetBehaviour dummyNetBehaviour) { @@ -98,7 +132,13 @@ protected override IEnumerator OnSetup() protected override void OnCreatePlayerPrefab() { + +#if NGO_DAMODE + DummyNetBehaviour.DistributedAuthority = m_DistributedAuthority; + m_PlayerPrefab.AddComponent(); +#else m_PlayerPrefab.AddComponent(); +#endif } [UnityTest] @@ -127,27 +167,34 @@ public IEnumerator TestEntireBufferIsCopiedOnNetworkVariableDelta() Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client side DummyNetBehaviour to register it was spawned!"); // Check that FieldWritten is written when dirty - serverComponent.NetVar.SetDirty(true); +#if NGO_DAMODE + var authorityComponent = m_DistributedAuthority ? clientComponent : serverComponent; + var nonAuthorityComponent = m_DistributedAuthority ? serverComponent : clientComponent; +#else + var authorityComponent = serverComponent; + var nonAuthorityComponent = clientComponent; +#endif + authorityComponent.NetVar.SetDirty(true); yield return s_DefaultWaitForTick; - Assert.True(serverComponent.NetVar.FieldWritten); - + Assert.True(authorityComponent.NetVar.FieldWritten); // Check that DeltaWritten is written when dirty - serverComponent.NetVar.SetDirty(true); + authorityComponent.NetVar.SetDirty(true); yield return s_DefaultWaitForTick; - Assert.True(serverComponent.NetVar.DeltaWritten); + Assert.True(authorityComponent.NetVar.DeltaWritten); + // Check that both FieldRead and DeltaRead were invoked on the client side - yield return WaitForConditionOrTimeOut(() => clientComponent.NetVar.FieldRead == true && clientComponent.NetVar.DeltaRead == true); + yield return WaitForConditionOrTimeOut(() => nonAuthorityComponent.NetVar.FieldRead == true && nonAuthorityComponent.NetVar.DeltaRead == true); var timedOutMessage = "Timed out waiting for client reads: "; if (s_GlobalTimeoutHelper.TimedOut) { - if (!clientComponent.NetVar.FieldRead) + if (!nonAuthorityComponent.NetVar.FieldRead) { timedOutMessage += "[FieldRead]"; } - if (!clientComponent.NetVar.DeltaRead) + if (!nonAuthorityComponent.NetVar.DeltaRead) { timedOutMessage += "[DeltaRead]"; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 7d47332f77..36a2724470 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -18,16 +18,20 @@ public static IEnumerable TestDataSource() { foreach (HostOrServer hostOrServer in Enum.GetValues(typeof(HostOrServer))) { +#if NGO_DAMODE + // DAMODE-TODO: Add support for distributed authority mode + if (hostOrServer == HostOrServer.DAHost) + { + continue; + } +#endif yield return new TestFixtureData(hostOrServer); } } protected override int NumberOfClients => 3; - public NetworkVariablePermissionTests(HostOrServer hostOrServer) - : base(hostOrServer) - { - } + public NetworkVariablePermissionTests(HostOrServer hostOrServer) : base(hostOrServer) { } private GameObject m_TestObjPrefab; private ulong m_TestObjId = 0; @@ -1210,7 +1214,7 @@ public void AssertArraysDoNotMatch(ref NativeArray a, ref NativeArray b private void TestValueTypeNativeArray(NativeArray testValue, NativeArray changedValue) where T : unmanaged { - Debug.Log($"Changing {ArrayStr(testValue)} to {ArrayStr(changedValue)}"); + VerboseDebug($"Changing {ArrayStr(testValue)} to {ArrayStr(changedValue)}"); var serverVariable = new NetworkVariable>(testValue); var clientVariable = new NetworkVariable>(new NativeArray(1, Allocator.Persistent)); using var writer = new FastBufferWriter(1024, Allocator.Temp, int.MaxValue); @@ -1277,7 +1281,7 @@ public void AssertListsDoNotMatch(ref List a, ref List b) private void TestList(List testValue, List changedValue) { - Debug.Log($"Changing {ListStr(testValue)} to {ListStr(changedValue)}"); + VerboseDebug($"Changing {ListStr(testValue)} to {ListStr(changedValue)}"); var serverVariable = new NetworkVariable>(testValue); var inPlaceList = new List(); var clientVariable = new NetworkVariable>(inPlaceList); @@ -1341,7 +1345,7 @@ public void AssertSetsDoNotMatch(ref HashSet a, ref HashSet b) where T private void TestHashSet(HashSet testValue, HashSet changedValue) where T : IEquatable { - Debug.Log($"Changing {HashSetStr(testValue)} to {HashSetStr(changedValue)}"); + VerboseDebug($"Changing {HashSetStr(testValue)} to {HashSetStr(changedValue)}"); var serverVariable = new NetworkVariable>(testValue); var inPlaceList = new HashSet(); var clientVariable = new NetworkVariable>(inPlaceList); @@ -1409,7 +1413,7 @@ public void AssertMapsDoNotMatch(ref Dictionary a, ref D private void TestDictionary(Dictionary testValue, Dictionary changedValue) where TKey : IEquatable { - Debug.Log($"Changing {DictionaryStr(testValue)} to {DictionaryStr(changedValue)}"); + VerboseDebug($"Changing {DictionaryStr(testValue)} to {DictionaryStr(changedValue)}"); var serverVariable = new NetworkVariable>(testValue); var inPlaceList = new Dictionary(); var clientVariable = new NetworkVariable>(inPlaceList); @@ -1480,7 +1484,7 @@ public void AssertListsDoNotMatch(ref NativeList a, ref NativeList b) w private void TestValueTypeNativeList(NativeList testValue, NativeList changedValue) where T : unmanaged { - Debug.Log($"Changing {NativeListStr(testValue)} to {NativeListStr(changedValue)}"); + VerboseDebug($"Changing {NativeListStr(testValue)} to {NativeListStr(changedValue)}"); var serverVariable = new NetworkVariable>(testValue); var inPlaceList = new NativeList(1, Allocator.Temp); var clientVariable = new NetworkVariable>(inPlaceList); @@ -1548,7 +1552,7 @@ public void AssertSetsDoNotMatch(ref NativeHashSet a, ref NativeHashSet private void TestValueTypeNativeHashSet(NativeHashSet testValue, NativeHashSet changedValue) where T : unmanaged, IEquatable { - Debug.Log($"Changing {NativeHashSetStr(testValue)} to {NativeHashSetStr(changedValue)}"); + VerboseDebug($"Changing {NativeHashSetStr(testValue)} to {NativeHashSetStr(changedValue)}"); var serverVariable = new NetworkVariable>(testValue); var inPlaceList = new NativeHashSet(1, Allocator.Temp); var clientVariable = new NetworkVariable>(inPlaceList); @@ -1623,7 +1627,7 @@ private void TestValueTypeNativeHashMap(NativeHashMap te where TKey : unmanaged, IEquatable where TVal : unmanaged { - Debug.Log($"Changing {NativeHashMapStr(testValue)} to {NativeHashMapStr(changedValue)}"); + VerboseDebug($"Changing {NativeHashMapStr(testValue)} to {NativeHashMapStr(changedValue)}"); var serverVariable = new NetworkVariable>(testValue); var inPlaceList = new NativeHashMap(1, Allocator.Temp); var clientVariable = new NetworkVariable>(inPlaceList); @@ -2166,10 +2170,10 @@ public string ArrayStr(NativeArray arr) where T : unmanaged changed2[originalSize + i] = item; } - Debug.Log($"Original: {ArrayStr(original)}"); - Debug.Log($"Changed: {ArrayStr(changed)}"); - Debug.Log($"Original2: {ArrayStr(original2)}"); - Debug.Log($"Changed2: {ArrayStr(changed2)}"); + VerboseDebug($"Original: {ArrayStr(original)}"); + VerboseDebug($"Changed: {ArrayStr(changed)}"); + VerboseDebug($"Original2: {ArrayStr(original2)}"); + VerboseDebug($"Changed2: {ArrayStr(changed2)}"); return (original, original2, changed, changed2); } @@ -2461,10 +2465,10 @@ public string DictionaryStr(Dictionary list) } - Debug.Log($"Original: {ListStr(original)}"); - Debug.Log($"Changed: {ListStr(changed)}"); - Debug.Log($"Original2: {ListStr(original2)}"); - Debug.Log($"Changed2: {ListStr(changed2)}"); + VerboseDebug($"Original: {ListStr(original)}"); + VerboseDebug($"Changed: {ListStr(changed)}"); + VerboseDebug($"Original2: {ListStr(original2)}"); + VerboseDebug($"Changed2: {ListStr(changed2)}"); return (original, original2, changed, changed2); } @@ -2528,10 +2532,10 @@ public string DictionaryStr(Dictionary list) changed2.Add(item); } - Debug.Log($"Original: {HashSetStr(original)}"); - Debug.Log($"Changed: {HashSetStr(changed)}"); - Debug.Log($"Original2: {HashSetStr(original2)}"); - Debug.Log($"Changed2: {HashSetStr(changed2)}"); + VerboseDebug($"Original: {HashSetStr(original)}"); + VerboseDebug($"Changed: {HashSetStr(changed)}"); + VerboseDebug($"Original2: {HashSetStr(original2)}"); + VerboseDebug($"Changed2: {HashSetStr(changed2)}"); return (original, original2, changed, changed2); } @@ -2619,10 +2623,10 @@ public string DictionaryStr(Dictionary list) changed2.Add(key, val); } - Debug.Log($"Original: {DictionaryStr(original)}"); - Debug.Log($"Changed: {DictionaryStr(changed)}"); - Debug.Log($"Original2: {DictionaryStr(original2)}"); - Debug.Log($"Changed2: {DictionaryStr(changed2)}"); + VerboseDebug($"Original: {DictionaryStr(original)}"); + VerboseDebug($"Changed: {DictionaryStr(changed)}"); + VerboseDebug($"Original2: {DictionaryStr(original2)}"); + VerboseDebug($"Changed2: {DictionaryStr(changed2)}"); return (original, original2, changed, changed2); } @@ -3908,10 +3912,10 @@ public string NativeHashMapStr(NativeHashMap list) } - Debug.Log($"Original: {NativeListStr(original)}"); - Debug.Log($"Changed: {NativeListStr(changed)}"); - Debug.Log($"Original2: {NativeListStr(original2)}"); - Debug.Log($"Changed2: {NativeListStr(changed2)}"); + VerboseDebug($"Original: {NativeListStr(original)}"); + VerboseDebug($"Changed: {NativeListStr(changed)}"); + VerboseDebug($"Original2: {NativeListStr(original2)}"); + VerboseDebug($"Changed2: {NativeListStr(changed2)}"); return (original, original2, changed, changed2); } @@ -3950,7 +3954,11 @@ public string NativeHashMapStr(NativeHashMap list) for (var i = 0; i < changed2Removes; ++i) { +#if UTP_TRANSPORT_2_0_ABOVE + var which = rand.Next(changed2.Count); +#else var which = rand.Next(changed2.Count()); +#endif T toRemove = default; foreach (var check in changed2) { @@ -3975,10 +3983,10 @@ public string NativeHashMapStr(NativeHashMap list) changed2.Add(item); } - Debug.Log($"Original: {NativeHashSetStr(original)}"); - Debug.Log($"Changed: {NativeHashSetStr(changed)}"); - Debug.Log($"Original2: {NativeHashSetStr(original2)}"); - Debug.Log($"Changed2: {NativeHashSetStr(changed2)}"); + VerboseDebug($"Original: {NativeHashSetStr(original)}"); + VerboseDebug($"Changed: {NativeHashSetStr(changed)}"); + VerboseDebug($"Original2: {NativeHashSetStr(original2)}"); + VerboseDebug($"Changed2: {NativeHashSetStr(changed2)}"); return (original, original2, changed, changed2); } @@ -4023,7 +4031,11 @@ public string NativeHashMapStr(NativeHashMap list) for (var i = 0; i < changed2Removes; ++i) { +#if UTP_TRANSPORT_2_0_ABOVE + var which = rand.Next(changed2.Count); +#else var which = rand.Next(changed2.Count()); +#endif TKey toRemove = default; foreach (var check in changed2) { @@ -4040,7 +4052,11 @@ public string NativeHashMapStr(NativeHashMap list) for (var i = 0; i < changed2Changes; ++i) { +#if UTP_TRANSPORT_2_0_ABOVE + var which = rand.Next(changed2.Count); +#else var which = rand.Next(changed2.Count()); +#endif TKey key = default; foreach (var check in changed2) { @@ -4067,10 +4083,10 @@ public string NativeHashMapStr(NativeHashMap list) changed2.Add(key, val); } - Debug.Log($"Original: {NativeHashMapStr(original)}"); - Debug.Log($"Changed: {NativeHashMapStr(changed)}"); - Debug.Log($"Original2: {NativeHashMapStr(original2)}"); - Debug.Log($"Changed2: {NativeHashMapStr(changed2)}"); + VerboseDebug($"Original: {NativeHashMapStr(original)}"); + VerboseDebug($"Changed: {NativeHashMapStr(changed)}"); + VerboseDebug($"Original2: {NativeHashMapStr(original2)}"); + VerboseDebug($"Changed2: {NativeHashMapStr(changed2)}"); return (original, original2, changed, changed2); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs index bbd9cb45fd..897f303308 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs @@ -7,20 +7,27 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SceneManagementState.SceneManagementEnabled, SessionModeTypes.DistributedAuthority)] + [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.DistributedAuthority)] + [TestFixture(SceneManagementState.SceneManagementEnabled, SessionModeTypes.ClientServer)] + [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.ClientServer)] +#else [TestFixture(SceneManagementState.SceneManagementEnabled)] [TestFixture(SceneManagementState.SceneManagementDisabled)] +#endif public class NetworkVisibilityTests : NetcodeIntegrationTest { - public enum SceneManagementState - { - SceneManagementEnabled, - SceneManagementDisabled - } + protected override int NumberOfClients => 1; private GameObject m_TestNetworkPrefab; private bool m_SceneManagementEnabled; +#if NGO_DAMODE + public NetworkVisibilityTests(SceneManagementState sceneManagementState, SessionModeTypes sessionModeType) : base(sessionModeType) +#else public NetworkVisibilityTests(SceneManagementState sceneManagementState) +#endif { m_SceneManagementEnabled = sceneManagementState == SceneManagementState.SceneManagementEnabled; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs index cc7b37e3de..7bf21e9341 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs @@ -18,6 +18,8 @@ public class OwnerModifiedObject : NetworkBehaviour, INetworkUpdateSystem internal static int Updates = 0; + public static bool EnableVerbose; + private void Awake() { MyNetworkList = new NetworkList(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); @@ -34,7 +36,11 @@ public void Changed(NetworkListEvent listEvent) expected++; listString += i.ToString(); } - Debug.Log($"[{NetworkManager.LocalClientId}] Value changed to {listString}"); + if (EnableVerbose) + { + Debug.Log($"[{NetworkManager.LocalClientId}] Value changed to {listString}"); + } + Updates++; } @@ -67,11 +73,16 @@ public void InitializeLastCient() NetworkUpdateLoop.RegisterAllNetworkUpdates(this); } } - +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif + [TestFixture(HostOrServer.Host)] public class OwnerModifiedTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; + public OwnerModifiedTests(HostOrServer hostOrServer) : base(hostOrServer) { } + protected override void OnCreatePlayerPrefab() { m_PlayerPrefab.AddComponent(); @@ -80,6 +91,7 @@ protected override void OnCreatePlayerPrefab() [UnityTest] public IEnumerator OwnerModifiedTest() { + OwnerModifiedObject.EnableVerbose = m_EnableVerboseDebug; // We use this to assure we are the "last client" connected. yield return CreateAndStartNewClient(); var ownerModLastClient = m_ClientNetworkManagers[2].LocalClient.PlayerObject.GetComponent(); @@ -89,7 +101,7 @@ public IEnumerator OwnerModifiedTest() foreach (var updateLoopType in System.Enum.GetValues(typeof(NetworkUpdateStage))) { ownerModLastClient.NetworkUpdateStageToCheck = (NetworkUpdateStage)updateLoopType; - Debug.Log($"Testing Update Stage: {ownerModLastClient.NetworkUpdateStageToCheck}"); + VerboseDebug($"Testing Update Stage: {ownerModLastClient.NetworkUpdateStageToCheck}"); ownerModLastClient.AddValues = true; yield return WaitForTicks(m_ServerNetworkManager, 5); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs index 7bd281644b..5f8501d889 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs @@ -50,7 +50,7 @@ public static void VerifyConsistency() public override void OnNetworkSpawn() { Objects[CurrentlySpawning, NetworkManager.LocalClientId] = GetComponent(); - Debug.Log($"Object index ({CurrentlySpawning}) spawned on client {NetworkManager.LocalClientId}"); + //Debug.Log($"Object index ({CurrentlySpawning}) spawned on client {NetworkManager.LocalClientId}"); } private void Awake() @@ -131,7 +131,7 @@ public IEnumerator OwnerPermissionTest() { // ==== Server-writable NetworkVariable ==== var gotException = false; - Debug.Log($"Writing to server-write variable on object {objectIndex} on client {clientWriting}"); + VerboseDebug($"Writing to server-write variable on object {objectIndex} on client {clientWriting}"); try { @@ -148,7 +148,7 @@ public IEnumerator OwnerPermissionTest() // ==== Owner-writable NetworkVariable ==== gotException = false; - Debug.Log($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}"); + VerboseDebug($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}"); try { @@ -165,7 +165,6 @@ public IEnumerator OwnerPermissionTest() // ==== Server-writable NetworkList ==== gotException = false; - Debug.Log($"Writing to server-write list on object {objectIndex} on client {clientWriting}"); try { @@ -182,7 +181,6 @@ public IEnumerator OwnerPermissionTest() // ==== Owner-writable NetworkList ==== gotException = false; - Debug.Log($"Writing to owner-write list on object {objectIndex} on client {clientWriting}"); try { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index 227b72ef79..f78dbf678b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -133,6 +133,7 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie } yield return WaitForConditionOrTimeOut(hooks); + Assert.False(s_GlobalTimeoutHelper.TimedOut); foreach (var client in m_ClientNetworkManagers) @@ -144,18 +145,18 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie } if (m_UseHost) { - Assert.IsTrue(client.ConnectedClientsIds.Contains(0ul)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(0ul), $"[Client-{client.LocalClientId}][Connected ({client.IsConnectedClient})] Still has client identifier 0!"); } for (var i = 1ul; i < 3ul; ++i) { if (i == disconnectedClient) { - Assert.IsFalse(client.ConnectedClientsIds.Contains(i)); + Assert.IsFalse(client.ConnectedClientsIds.Contains(i), $"[Client-{client.LocalClientId}][Connected ({client.IsConnectedClient})] Still has client identifier {i}!"); } else { - Assert.IsTrue(client.ConnectedClientsIds.Contains(i)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(i), $"[Client-{client.LocalClientId}][Connected ({client.IsConnectedClient})] Still has client identifier {i}!"); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index cb0b608c29..8d32ebb553 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -8,16 +8,46 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(RigidbodyInterpolation.Interpolate, true, true)] // This should be allowed under all condistions when using Rigidbody motion + [TestFixture(RigidbodyInterpolation.Extrapolate, true, true)] // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation + [TestFixture(RigidbodyInterpolation.Extrapolate, false, true)] // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation + [TestFixture(RigidbodyInterpolation.Interpolate, true, false)] // This should not allow kinematic instances to have Rigidbody interpolation enabled + [TestFixture(RigidbodyInterpolation.Interpolate, false, false)] // Testing that rigid body interpolation remains the same if NT interpolate is disabled +#endif + public class NetworkRigidbodyTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; +#if NGO_DAMODE + private bool m_NetworkTransformInterpolate; + private bool m_UseRigidBodyForMotion; + private RigidbodyInterpolation m_RigidbodyInterpolation; + + public NetworkRigidbodyTest(RigidbodyInterpolation rigidbodyInterpolation, bool networkTransformInterpolate, bool useRigidbodyForMotion) + { + m_RigidbodyInterpolation = rigidbodyInterpolation; + m_NetworkTransformInterpolate = networkTransformInterpolate; + m_UseRigidBodyForMotion = useRigidbodyForMotion; + } +#endif + protected override void OnCreatePlayerPrefab() { +#if NGO_DAMODE + var networkTransform = m_PlayerPrefab.AddComponent(); + networkTransform.Interpolate = m_NetworkTransformInterpolate; + var rigidbody = m_PlayerPrefab.AddComponent(); + rigidbody.interpolation = m_RigidbodyInterpolation; + var networkRigidbody = m_PlayerPrefab.AddComponent(); + networkRigidbody.UseRigidBodyForMotion = m_UseRigidBodyForMotion; +#else m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); m_PlayerPrefab.GetComponent().interpolation = RigidbodyInterpolation.Interpolate; +#endif } /// @@ -28,39 +58,68 @@ protected override void OnCreatePlayerPrefab() public IEnumerator TestRigidbodyKinematicEnableDisable() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); - var serverPlayer = serverClientPlayerResult.Result.gameObject; + var serverClientPlayerInstance = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject; // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); - var clientPlayer = clientClientPlayerResult.Result.gameObject; + var clientPlayerInstance = m_ClientNetworkManagers[0].LocalClient.PlayerObject; - Assert.IsNotNull(serverPlayer, "serverPlayer is not null"); - Assert.IsNotNull(clientPlayer, "clientPlayer is not null"); + Assert.IsNotNull(serverClientPlayerInstance, $"{nameof(serverClientPlayerInstance)} is null!"); + Assert.IsNotNull(clientPlayerInstance, $"{nameof(clientPlayerInstance)} is null!"); - yield return WaitForTicks(m_ServerNetworkManager, 3); + var serverClientInstanceRigidBody = serverClientPlayerInstance.GetComponent(); + var clientRigidBody = clientPlayerInstance.GetComponent(); +#if NGO_DAMODE + if (m_UseRigidBodyForMotion) + { + // Server authoritative NT should yield non-kinematic mode for the server-side player instance + Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); - // server rigidbody has authority and should not be kinematic - Assert.True(serverPlayer.GetComponent().isKinematic == false, "serverPlayer kinematic"); - Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverPlayer.GetComponent().interpolation, "server equal interpolate"); + // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. + Assert.AreEqual(m_RigidbodyInterpolation, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + + $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - // client rigidbody has no authority and should have a kinematic mode of true - Assert.True(clientPlayer.GetComponent().isKinematic, "clientPlayer kinematic"); - Assert.AreEqual(RigidbodyInterpolation.None, clientPlayer.GetComponent().interpolation, "client equal interpolate"); + // Server authoritative NT should yield kinematic mode for the client-side player instance + Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); - // despawn the server player (but keep it around on the server) - serverPlayer.GetComponent().Despawn(false); + // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used + Assert.AreEqual(RigidbodyInterpolation.Interpolate, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + + $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); + } + else +#endif + { + // server rigidbody has authority and should not be kinematic + Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); + Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + + $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - yield return WaitForTicks(m_ServerNetworkManager, 3); + // Server authoritative NT should yield kinematic mode for the client-side player instance + Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); - // When despawned, we should always be kinematic (i.e. don't apply physics when despawned) - Assert.IsTrue(serverPlayer.GetComponent().isKinematic == true, "serverPlayer second kinematic"); +#if NGO_DAMODE + // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation + if (!m_NetworkTransformInterpolate) + { + Assert.AreEqual(RigidbodyInterpolation.Interpolate, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + + $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); + } + else +#endif + { + Assert.AreEqual(RigidbodyInterpolation.None, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + + $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.None)}!"); + } + } - yield return WaitForTicks(m_ServerNetworkManager, 3); + // despawn the server player (but keep it around on the server) + serverClientPlayerInstance.Despawn(false); - Assert.IsTrue(clientPlayer == null, "clientPlayer being null"); // safety check that object is actually despawned. + yield return WaitForConditionOrTimeOut(() => !serverClientPlayerInstance.IsSpawned && !clientPlayerInstance.IsSpawned); + AssertOnTimeout("Timed out waiting for client player to despawn on both server and client!"); + + // When despawned, we should always be kinematic (i.e. don't apply physics when despawned) + Assert.True(serverClientInstanceRigidBody.isKinematic, $"[Server-Side][Despawned] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic when despawned!"); + Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!"); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs index bd947f2435..96f4eff638 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs @@ -6,6 +6,9 @@ namespace Unity.Netcode.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class PlayerObjectTests : NetcodeIntegrationTest @@ -25,6 +28,10 @@ protected override void OnServerAndClientsCreated() [UnityTest] public IEnumerator SpawnAndReplaceExistingPlayerObject() { +#if NGO_DAMODE + yield return WaitForConditionOrTimeOut(() => m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_ClientNetworkManagers[0].LocalClientId)); + AssertOnTimeout("Timed out waiting for client-side player object to spawn!"); +#endif // Get the server-side player NetworkObject var originalPlayer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; // Get the client-side player NetworkObject @@ -33,7 +40,12 @@ public IEnumerator SpawnAndReplaceExistingPlayerObject() // Create a new player prefab instance var newPlayer = Object.Instantiate(m_NewPlayerToSpawn); var newPlayerNetworkObject = newPlayer.GetComponent(); +#if NGO_DAMODE + // In distributed authority mode, the client owner spawns its new player + newPlayerNetworkObject.NetworkManagerOwner = m_DistributedAuthority ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; +#else newPlayerNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; +#endif // Spawn this instance as a new player object for the client who already has an assigned player object newPlayerNetworkObject.SpawnAsPlayerObject(m_ClientNetworkManagers[0].LocalClientId); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs index b26d5b2486..ecb9337615 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs @@ -5,7 +5,6 @@ using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine.TestTools; -using Debug = UnityEngine.Debug; using Vector3 = UnityEngine.Vector3; namespace Unity.Netcode.RuntimeTests @@ -121,7 +120,7 @@ public IEnumerator TestRpcs() localClienRpcTestNB.OnClient_Rpc += () => { - Debug.Log("ClientRpc received on client object"); + VerboseDebug("ClientRpc received on client object"); hasReceivedClientRpcRemotely = true; }; @@ -139,14 +138,14 @@ public IEnumerator TestRpcs() serverClientRpcTestNB.OnServer_Rpc += (clientId, param) => { - Debug.Log("ServerRpc received on server object"); + VerboseDebug("ServerRpc received on server object"); Assert.True(param.Receive.SenderClientId == clientId); hasReceivedServerRpc = true; }; serverClientRpcTestNBFloat.OnServer_Rpc += (clientId, param) => { - Debug.Log("ServerRpc (float) received on server object"); + VerboseDebug("ServerRpc (float) received on server object"); Assert.True(param.Receive.SenderClientId == clientId); hasReceivedFloatServerRpc = true; }; @@ -154,7 +153,7 @@ public IEnumerator TestRpcs() serverClientRpcTestNB.OnClient_Rpc += () => { // The RPC invoked locally. (Weaver failure?) - Debug.Log("ClientRpc received on server object"); + VerboseDebug("ClientRpc received on server object"); hasReceivedClientRpcLocally = true; }; @@ -167,7 +166,7 @@ public IEnumerator TestRpcs() #endif param4) => { - Debug.Log("TypedServerRpc received on server object"); + VerboseDebug("TypedServerRpc received on server object"); Assert.AreEqual(param1, vector3); Assert.AreEqual(param2.Length, vector3s.Length); Assert.AreEqual(param2[0], vector3s[0]); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs index 9d09d67772..3d57e4d4ef 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs @@ -889,9 +889,9 @@ public IEnumerator TestValueType(T firstTest, T secondTest) where T : unmanag clientObject.OnReceived = o => { receivedValue = o; - Debug.Log($"Received value {o}"); + VerboseDebug($"Received value {o}"); }; - Debug.Log($"Sending first RPC with {firstTest}"); + VerboseDebug($"Sending first RPC with {firstTest}"); method.Invoke(serverObject, new object[] { firstTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); @@ -904,7 +904,7 @@ public IEnumerator TestValueType(T firstTest, T secondTest) where T : unmanag receivedValue = null; - Debug.Log($"Sending second RPC with {secondTest}"); + VerboseDebug($"Sending second RPC with {secondTest}"); method.Invoke(serverObject, new object[] { secondTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); @@ -943,9 +943,9 @@ public IEnumerator TestValueTypeArray(T[] firstTest, T[] secondTest) where T clientObject.OnReceived = o => { receivedValue = o; - Debug.Log($"Received value {o}"); + VerboseDebug($"Received value {o}"); }; - Debug.Log($"Sending first RPC with {firstTest}"); + VerboseDebug($"Sending first RPC with {firstTest}"); method.Invoke(serverObject, new object[] { firstTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); @@ -958,7 +958,7 @@ public IEnumerator TestValueTypeArray(T[] firstTest, T[] secondTest) where T receivedValue = null; - Debug.Log($"Sending second RPC with {secondTest}"); + VerboseDebug($"Sending second RPC with {secondTest}"); method.Invoke(serverObject, new object[] { secondTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); @@ -1005,9 +1005,9 @@ public IEnumerator TestValueTypeNativeArray(NativeArray firstTest, NativeA Assert.AreEqual(o.GetType(), typeof(NativeArray)); var oAsArray = (NativeArray)o; receivedValue = new NativeArray(oAsArray, Allocator.Persistent); - Debug.Log($"Received value {o}"); + VerboseDebug($"Received value {o}"); }; - Debug.Log($"Sending first RPC with {firstTest}"); + VerboseDebug($"Sending first RPC with {firstTest}"); method.Invoke(serverObject, new object[] { firstTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); @@ -1020,7 +1020,7 @@ public IEnumerator TestValueTypeNativeArray(NativeArray firstTest, NativeA receivedValue = new NativeArray(); - Debug.Log($"Sending second RPC with {secondTest}"); + VerboseDebug($"Sending second RPC with {secondTest}"); method.Invoke(serverObject, new object[] { secondTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); @@ -1066,9 +1066,9 @@ public IEnumerator TestValueTypeNativeList(NativeList firstTest, NativeLis { receivedValue.Add(item); } - Debug.Log($"Received value {o}"); + VerboseDebug($"Received value {o}"); }; - Debug.Log($"Sending first RPC with {firstTest}"); + VerboseDebug($"Sending first RPC with {firstTest}"); method.Invoke(serverObject, new object[] { firstTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); @@ -1081,7 +1081,7 @@ public IEnumerator TestValueTypeNativeList(NativeList firstTest, NativeLis receivedValue = new NativeList(); - Debug.Log($"Sending second RPC with {secondTest}"); + VerboseDebug($"Sending second RPC with {secondTest}"); method.Invoke(serverObject, new object[] { secondTest }); yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs index cbd7b99d9d..fe7d935f6c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs @@ -80,7 +80,7 @@ public IEnumerator TestClientTimeInitializationOnConnect([Values(0, 1f)] float s private void NetworkTickSystemOnTick() { - Debug.Log(m_Client.NetworkTickSystem.ServerTime.Tick); + //Debug.Log(m_Client.NetworkTickSystem.ServerTime.Tick); m_ClientTickCounter++; } @@ -88,7 +88,7 @@ private void ClientOnOnClientConnectedCallback(ulong id) { // client connected to server m_ConnectedTick = m_Client.NetworkTickSystem.ServerTime.Tick; - Debug.Log($"Connected tick: {m_ConnectedTick}"); + //Debug.Log($"Connected tick: {m_ConnectedTick}"); } [UnityTearDown] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 48f0472e8d..85305970e0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -221,7 +221,7 @@ public IEnumerator TransformInterpolationTest() // and how they move var timeOutHelper = new TimeoutFrameCountHelper(10); yield return WaitForConditionOrTimeOut(spawnedObjectNetworkTransform.ReachedTargetLocalSpaceTransitionCount, timeOutHelper); - Debug.Log($"[TransformInterpolationTest] Wait condition reached or timed out. Frame Count ({timeOutHelper.GetFrameCount()}) | Time Elapsed ({timeOutHelper.GetTimeElapsed()})"); + VerboseDebug($"[TransformInterpolationTest] Wait condition reached or timed out. Frame Count ({timeOutHelper.GetFrameCount()}) | Time Elapsed ({timeOutHelper.GetTimeElapsed()})"); AssertOnTimeout($"Failed to reach desired local to world space transitions in the given time!", timeOutHelper); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs index 86ce82b74e..2f2a2a3d6a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -96,6 +96,20 @@ public void DefaultToClientsAndHostRpc() OnRpcReceived(); } +#if NGO_DAMODE + [Rpc(SendTo.Authority)] + public void DefaultToAuthorityRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotAuthority)] + public void DefaultToNotAuthorityRpc() + { + OnRpcReceived(); + } +#endif + // RPCs with parameters [Rpc(SendTo.Everyone)] @@ -146,6 +160,20 @@ public void DefaultToClientsAndHostWithParamsRpc(int i, bool b, float f, string OnRpcReceivedWithParams(i, b, f, s); } +#if NGO_DAMODE + [Rpc(SendTo.Authority)] + public void DefaultToAuthorityWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotAuthority)] + public void DefaultToNotAuthorityWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } +#endif + // RPCs with RPC parameters [Rpc(SendTo.Everyone)] @@ -204,6 +232,22 @@ public void DefaultToClientsAndHostWithRpcParamsRpc(RpcParams rpcParams) ReceivedFrom = rpcParams.Receive.SenderClientId; } +#if NGO_DAMODE + [Rpc(SendTo.Authority)] + public void DefaultToAuthorityWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.NotAuthority)] + public void DefaultToNotAuthorityWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } +#endif + // RPCs with parameters and RPC parameters @@ -255,6 +299,20 @@ public void DefaultToClientsAndHostWithParamsAndRpcParamsRpc(int i, bool b, floa OnRpcReceivedWithParams(i, b, f, s); } +#if NGO_DAMODE + [Rpc(SendTo.Authority)] + public void DefaultToAuthorityWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotAuthority)] + public void DefaultToNotAuthorityWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } +#endif + // RPCs with AllowTargetOverride = true // AllowTargetOverried is implied with SpecifiedInParams and does not need to be stated @@ -313,6 +371,20 @@ public void DefaultToClientsAndHostAllowOverrideRpc(RpcParams rpcParams) OnRpcReceived(); } +#if NGO_DAMODE + [Rpc(SendTo.Authority, AllowTargetOverride = true)] + public void DefaultToAuthorityAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotAuthority, AllowTargetOverride = true)] + public void DefaultToNotAuthorityAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } +#endif + // RPCs with DeferLocal = true [Rpc(SendTo.Everyone, DeferLocal = true)] @@ -357,6 +429,20 @@ public void DefaultToClientsAndHostDeferLocalRpc(RpcParams rpcParams) OnRpcReceived(); } +#if NGO_DAMODE + [Rpc(SendTo.Authority, DeferLocal = true)] + public void DefaultToAuthorityDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotAuthority, DeferLocal = true)] + public void DefaultToNotAuthorityDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } +#endif + // RPCs with RequireOwnership = true [Rpc(SendTo.Everyone, RequireOwnership = true)] @@ -413,6 +499,20 @@ public void SpecifiedInParamsRequireOwnershipRpc(RpcParams rpcParams) OnRpcReceived(); } +#if NGO_DAMODE + [Rpc(SendTo.Authority, RequireOwnership = true)] + public void DefaultToAuthorityRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotAuthority, RequireOwnership = true)] + public void DefaultToNotAuthorityRequireOwnershipRpc() + { + OnRpcReceived(); + } +#endif + // Mutual RPC Recursion @@ -814,6 +914,28 @@ public void VerifySentToNotMe(ulong objectOwner, ulong sender, string methodName VerifySentToNotId(objectOwner, sender, sender, methodName, false); } +#if NGO_DAMODE + public void VerifySentToAuthority(ulong objectOwner, ulong sender, string methodName) + { + var receiver = objectOwner; + if (!m_DistributedAuthority) + { + receiver = NetworkManager.ServerClientId; + } + VerifySentToId(objectOwner, sender, receiver, methodName, false); + } + + public void VerifySentToNotAuthority(ulong objectOwner, ulong sender, string methodName) + { + var receiver = objectOwner; + if (!m_DistributedAuthority) + { + receiver = NetworkManager.ServerClientId; + } + VerifySentToNotId(objectOwner, sender, receiver, methodName, false); + } +#endif + public void VerifySentToOwnerWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) { VerifySentToId(objectOwner, sender, objectOwner, methodName, true); @@ -856,6 +978,28 @@ public void VerifySentToNotMeWithReceivedFrom(ulong objectOwner, ulong sender, s VerifySentToNotId(objectOwner, sender, sender, methodName, true); } +#if NGO_DAMODE + public void VerifySentToAuthorityWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + var receiver = objectOwner; + if (!m_DistributedAuthority) + { + receiver = NetworkManager.ServerClientId; + } + VerifySentToId(objectOwner, sender, receiver, methodName, true); + } + + public void VerifySentToNotAuthorityWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + var receiver = objectOwner; + if (!m_DistributedAuthority) + { + receiver = NetworkManager.ServerClientId; + } + VerifySentToNotId(objectOwner, sender, receiver, methodName, true); + } +#endif + public void VerifySentToOwnerWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) { VerifySentToIdWithParams(objectOwner, sender, objectOwner, methodName, i, b, f, s); @@ -898,6 +1042,28 @@ public void VerifySentToNotMeWithParams(ulong objectOwner, ulong sender, string VerifySentToNotIdWithParams(objectOwner, sender, sender, methodName, i, b, f, s); } +#if NGO_DAMODE + public void VerifySentToAuthorityWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + var receiver = objectOwner; + if (!m_DistributedAuthority) + { + receiver = NetworkManager.ServerClientId; + } + VerifySentToIdWithParams(objectOwner, sender, receiver, methodName, i, b, f, s); + } + + public void VerifySentToNotAuthorityWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + var receiver = objectOwner; + if (!m_DistributedAuthority) + { + receiver = NetworkManager.ServerClientId; + } + VerifySentToNotIdWithParams(objectOwner, sender, receiver, methodName, i, b, f, s); + } +#endif + public void RethrowTargetInvocationException(Action action) { try @@ -911,6 +1077,9 @@ public void RethrowTargetInvocationException(Action action) } } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSendingNoOverride : UniversalRpcTestsBase @@ -923,7 +1092,12 @@ public UniversalRpcTestSendingNoOverride(HostOrServer hostOrServer) : base(hostO [Test] public void TestSendingNoOverride( // Excludes SendTo.SpecifiedInParams +#if NGO_DAMODE + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, + SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, +#else [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, +#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -941,6 +1115,9 @@ public void TestSendingNoOverride( } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSenderClientId : UniversalRpcTestsBase @@ -953,7 +1130,11 @@ public UniversalRpcTestSenderClientId(HostOrServer hostOrServer) : base(hostOrSe [Test] public void TestSenderClientId( // Excludes SendTo.SpecifiedInParams +#if NGO_DAMODE + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, +#else [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, +#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -971,6 +1152,9 @@ public void TestSenderClientId( } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSendingNoOverrideWithParams : UniversalRpcTestsBase @@ -983,7 +1167,11 @@ public UniversalRpcTestSendingNoOverrideWithParams(HostOrServer hostOrServer) : [Test] public void TestSendingNoOverrideWithParams( // Excludes SendTo.SpecifiedInParams +#if NGO_DAMODE + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, +#else [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, +#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1013,6 +1201,9 @@ public void TestSendingNoOverrideWithParams( } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSendingNoOverrideWithParamsAndRpcParams : UniversalRpcTestsBase @@ -1025,7 +1216,11 @@ public UniversalRpcTestSendingNoOverrideWithParamsAndRpcParams(HostOrServer host [Test] public void TestSendingNoOverrideWithParamsAndRpcParams( // Excludes SendTo.SpecifiedInParams +#if NGO_DAMODE + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, +#else [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, +#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1055,6 +1250,9 @@ public void TestSendingNoOverrideWithParamsAndRpcParams( } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestRequireOwnership : UniversalRpcTestsBase @@ -1067,7 +1265,11 @@ public UniversalRpcTestRequireOwnership(HostOrServer hostOrServer) : base(hostOr [Test] public void TestRequireOwnership( // Excludes SendTo.SpecifiedInParams +#if NGO_DAMODE + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, +#else [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, +#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1091,6 +1293,9 @@ public void TestRequireOwnership( } } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestDisallowedOverride : UniversalRpcTestsBase @@ -1100,10 +1305,15 @@ public UniversalRpcTestDisallowedOverride(HostOrServer hostOrServer) : base(host } + // Add both authority and nonauthority [Test] public void TestDisallowedOverride( // Excludes SendTo.SpecifiedInParams +#if NGO_DAMODE + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, +#else [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, +#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender) { @@ -1135,6 +1345,7 @@ public UniversalRpcTestSendingWithTargetOverride(HostOrServer hostOrServer) : ba } + // Look at the implementations and add both [Test] public void TestSendingWithTargetOverride( [Values] SendTo defaultSendTo, @@ -1391,6 +1602,9 @@ public UniversalRpcTestDefaultSendToSpecifiedInParamsSendingToServerAndOwner(Hos } } +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestDeferLocal : UniversalRpcTestsBase @@ -1447,6 +1661,18 @@ public UniversalRpcTestDeferLocal(HostOrServer hostOrServer) : base(hostOrServer [TestCase(SendTo.ClientsAndHost, 2u, 0u)] [TestCase(SendTo.ClientsAndHost, 2u, 1u)] [TestCase(SendTo.ClientsAndHost, 2u, 2u)] + +#if NGO_DAMODE + [TestCase(SendTo.Authority, 0u, 0u)] + [TestCase(SendTo.Authority, 1u, 1u)] + [TestCase(SendTo.Authority, 2u, 2u)] + [TestCase(SendTo.NotAuthority, 0u, 1u)] + [TestCase(SendTo.NotAuthority, 0u, 2u)] + [TestCase(SendTo.NotAuthority, 1u, 0u)] + [TestCase(SendTo.NotAuthority, 1u, 2u)] + [TestCase(SendTo.NotAuthority, 2u, 0u)] + [TestCase(SendTo.NotAuthority, 2u, 1u)] +#endif public void TestDeferLocal( SendTo defaultSendTo, ulong objectOwner, @@ -1459,6 +1685,15 @@ ulong sender // Just consider this case a success... return; } + +#if NGO_DAMODE + // Similar to above, since Server and NotServer are already tested we can consider this a success + if (!m_DistributedAuthority && defaultSendTo == SendTo.Authority || defaultSendTo == SendTo.NotAuthority) + { + return; + } +#endif + var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc"; var verifyMethodName = $"VerifySentTo{defaultSendTo}"; var senderObject = GetPlayerObject(objectOwner, sender); @@ -1521,6 +1756,17 @@ ulong sender [TestCase(SendTo.ClientsAndHost, 2u, 0u)] [TestCase(SendTo.ClientsAndHost, 2u, 1u)] [TestCase(SendTo.ClientsAndHost, 2u, 2u)] +#if NGO_DAMODE + [TestCase(SendTo.Authority, 0u, 0u)] + [TestCase(SendTo.Authority, 1u, 1u)] + [TestCase(SendTo.Authority, 2u, 2u)] + [TestCase(SendTo.NotAuthority, 0u, 1u)] + [TestCase(SendTo.NotAuthority, 0u, 2u)] + [TestCase(SendTo.NotAuthority, 1u, 0u)] + [TestCase(SendTo.NotAuthority, 1u, 2u)] + [TestCase(SendTo.NotAuthority, 2u, 0u)] + [TestCase(SendTo.NotAuthority, 2u, 1u)] +#endif public void TestDeferLocalOverrideToTrue( SendTo defaultSendTo, ulong objectOwner, @@ -1533,6 +1779,14 @@ ulong sender // Just consider this case a success... return; } +#if NGO_DAMODE + // Similar to above, since Server and NotServer are already tested we can consider this a success + if (!m_DistributedAuthority && defaultSendTo == SendTo.Authority || defaultSendTo == SendTo.NotAuthority) + { + return; + } +#endif + var sendMethodName = $"DefaultTo{defaultSendTo}WithRpcParamsRpc"; var verifyMethodName = $"VerifySentTo{defaultSendTo}"; var senderObject = GetPlayerObject(objectOwner, sender); @@ -1595,6 +1849,17 @@ ulong sender [TestCase(SendTo.ClientsAndHost, 2u, 0u)] [TestCase(SendTo.ClientsAndHost, 2u, 1u)] [TestCase(SendTo.ClientsAndHost, 2u, 2u)] +#if NGO_DAMODE + [TestCase(SendTo.Authority, 0u, 0u)] + [TestCase(SendTo.Authority, 1u, 1u)] + [TestCase(SendTo.Authority, 2u, 2u)] + [TestCase(SendTo.NotAuthority, 0u, 1u)] + [TestCase(SendTo.NotAuthority, 0u, 2u)] + [TestCase(SendTo.NotAuthority, 1u, 0u)] + [TestCase(SendTo.NotAuthority, 1u, 2u)] + [TestCase(SendTo.NotAuthority, 2u, 0u)] + [TestCase(SendTo.NotAuthority, 2u, 1u)] +#endif public void TestDeferLocalOverrideToFalse( SendTo defaultSendTo, ulong objectOwner, @@ -1607,6 +1872,14 @@ ulong sender // Just consider this case a success... return; } +#if NGO_DAMODE + // Similar to above, since Server and NotServer are already tested we can consider this a success + if (!m_DistributedAuthority && defaultSendTo == SendTo.Authority || defaultSendTo == SendTo.NotAuthority) + { + return; + } +#endif + var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc"; var verifyMethodName = $"VerifySentTo{defaultSendTo}"; var senderObject = GetPlayerObject(objectOwner, sender); diff --git a/com.unity.netcode.gameobjects/ValidationExceptions.json b/com.unity.netcode.gameobjects/ValidationExceptions.json index 2d816124e2..8db7cad09c 100644 --- a/com.unity.netcode.gameobjects/ValidationExceptions.json +++ b/com.unity.netcode.gameobjects/ValidationExceptions.json @@ -1,10 +1,10 @@ { "ErrorExceptions": [ - { - "ValidationTest": "API Validation", - "ExceptionMessage": "Additions require a new minor or major version.", - "PackageVersion": "1.5.2" - } + { + "ValidationTest": "API Validation", + "ExceptionMessage": "Breaking changes require a new major version.", + "PackageVersion": "1.8.0" + } ], "WarningExceptions": [] } \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 5a0c239d66..bd8e20aeb1 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,10 +2,10 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.9.0", - "unity": "2021.3", + "version": "2.0.0-exp", + "unity": "2022.3", "dependencies": { - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.4.0" + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.transport": "2.2.1" } } diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs index 72f08fd45a..0f8d756e74 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; -using TestProject.ManualTests; using Unity.Netcode; using Unity.Netcode.Components; using UnityEngine; using Random = UnityEngine.Random; -namespace TestProject.RuntimeTests +namespace TestProject.ManualTests { public class InSceneParentChildHandler : NetworkBehaviour { - public static InSceneParentChildHandler ServerRootParent; + public static InSceneParentChildHandler AuthorityRootParent; public static bool EnableVerboseDebug = true; public static bool AddNetworkTransform; public static bool WorldPositionStays; @@ -38,13 +37,13 @@ public class InSceneParentChildHandler : NetworkBehaviour private NetworkTransform m_NetworkTransform; - public static Dictionary ServerRelativeInstances = new Dictionary(); + public static Dictionary AuthorityRelativeInstances = new Dictionary(); public static Dictionary> ClientRelativeInstances = new Dictionary>(); public static void ResetInstancesTracking(bool enableVerboseDebug) { EnableVerboseDebug = enableVerboseDebug; - ServerRelativeInstances.Clear(); + AuthorityRelativeInstances.Clear(); ClientRelativeInstances.Clear(); } @@ -121,9 +120,23 @@ private void RemoveParent(Transform child, bool worldPositionStays = true) } } + /// + /// DANGO-TODO: Run test where we remove the DAHost as the authority and see what breaks (if anything) + /// For now, handle checking authority outselves. + /// + /// + private bool CheckForAuthority() + { +#if NGO_DAMODE + return NetworkObject.HasAuthority; +#else + return IsServer; +#endif + } + public void DeparentAllChildren(bool worldPositionStays = true) { - if (IsRootParent && IsServer) + if (IsRootParent && CheckForAuthority()) { var lastChild = GetLastChild(transform); if (lastChild != null) @@ -154,7 +167,7 @@ private void ParentChild(InSceneParentChildHandler child, bool worldPositionStay public void ReParentAllChildren(bool worldPositionStays = true) { - if (IsRootParent && IsServer) + if (IsRootParent && CheckForAuthority()) { ParentChild(m_Child, worldPositionStays); } @@ -162,7 +175,7 @@ public void ReParentAllChildren(bool worldPositionStays = true) public override void OnNetworkSpawn() { - if (IsServer) + if (CheckForAuthority()) { LogMessage($"[{NetworkObjectId}] Pos = ({m_TargetLocalPosition}) | Rotation ({m_TargetLocalRotation}) | Scale ({m_TargetLocalScale})"); if (AddNetworkTransform) @@ -172,9 +185,13 @@ public override void OnNetworkSpawn() } if (IsRootParent) { - ServerRootParent = this; + AuthorityRootParent = this; + } + + if (!AuthorityRelativeInstances.ContainsKey(NetworkObjectId)) + { + AuthorityRelativeInstances.Add(NetworkObjectId, this); } - ServerRelativeInstances.Add(NetworkObjectId, this); } else { @@ -193,7 +210,7 @@ public override void OnNetworkSpawn() public void DeparentSetValuesAndReparent() { - if (IsServer && IsRootParent) + if (IsRootParent && CheckForAuthority()) { // Back to back de-parenting and re-parenting s_GenerateRandomValues = true; @@ -209,7 +226,7 @@ public void DeparentSetValuesAndReparent() /// public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { - if (!IsServer || !IsSpawned || parentNetworkObject != null || !s_GenerateRandomValues) + if (!CheckForAuthority() || !IsSpawned || parentNetworkObject != null || !s_GenerateRandomValues) { return; } @@ -225,10 +242,11 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj base.OnNetworkObjectParentChanged(parentNetworkObject); } + private bool m_RequestSent; private void LateUpdate() { - if (!IsSpawned || !IsServer || NetworkManagerTestDisabler.IsIntegrationTest) + if (!IsSpawned || !CheckForAuthority() || NetworkManagerTestDisabler.IsIntegrationTest) { return; } @@ -250,6 +268,253 @@ private void LateUpdate() { RootParent.DeparentSetValuesAndReparent(); } + + + if (Input.GetKeyDown(KeyCode.Space)) + { + m_RequestSent = true; + RequestTransformInfoRpc(); + } + } + + public void CheckChildren() + { +#if NGO_DAMODE + if (!NetworkObject.HasAuthority || m_RequestSent || AuthorityRootParent != this) +#else + if (!NetworkManager.IsServer || m_RequestSent || AuthorityRootParent != this) +#endif + { + return; + } + + m_RequestSent = true; + RequestTransformInfoRpc(); + } + + [Rpc(SendTo.NotOwner)] + private void RequestTransformInfoRpc() + { + var nonAuthInstance = ClientRelativeInstances[NetworkManager.LocalClientId]; + var childrenInfo = new ChildrenInfo() + { + Children = new List() + }; + + foreach (var instance in nonAuthInstance) + { + var childInfo = new ChildInfo() + { + InfoType = ChildInfoType.AuthRelative, + Id = instance.Key, + Position = instance.Value.transform.position, + Rotation = instance.Value.transform.eulerAngles, + Scale = instance.Value.transform.localScale, + }; + childrenInfo.Children.Add(childInfo); + } + + var autoSync = ParentingAutoSyncManager.ClientInstances[NetworkManager.LocalClientId]; + var count = 0; + foreach (var instance in autoSync.NetworkObjectAutoSyncOffTransforms) + { + var childInfo = new ChildInfo() + { + InfoType = ChildInfoType.AutoSyncOff, + Id = (ulong)count, + Position = instance.position, + Rotation = instance.eulerAngles, + Scale = instance.localScale, + }; + count++; + childrenInfo.Children.Add(childInfo); + } + count = 0; + foreach (var instance in autoSync.NetworkObjectAutoSyncOnTransforms) + { + var childInfo = new ChildInfo() + { + InfoType = ChildInfoType.AutoSyncOn, + Id = (ulong)count, + Position = instance.position, + Rotation = instance.eulerAngles, + Scale = instance.localScale, + }; + count++; + childrenInfo.Children.Add(childInfo); + } + count = 0; + foreach (var instance in autoSync.GameObjectAutoSyncOffTransforms) + { + var childInfo = new ChildInfo() + { + InfoType = ChildInfoType.AutoSyncGameObjectOff, + Id = (ulong)count, + Position = instance.position, + Rotation = instance.eulerAngles, + Scale = instance.localScale, + }; + count++; + childrenInfo.Children.Add(childInfo); + } + count = 0; + foreach (var instance in autoSync.GameObjectAutoSyncOnTransforms) + { + var childInfo = new ChildInfo() + { + InfoType = ChildInfoType.AutoSyncGameObjectOn, + Id = (ulong)count, + Position = instance.position, + Rotation = instance.eulerAngles, + Scale = instance.localScale, + }; + count++; + childrenInfo.Children.Add(childInfo); + } + SendTransformInfoRpc(childrenInfo); + } + + [Rpc(SendTo.Owner)] + private void SendTransformInfoRpc(ChildrenInfo childrenInfo) + { + m_RequestSent = false; + var errorCount = 0; + var autoSync = ParentingAutoSyncManager.ServerInstance; + var position = Vector3.zero; + var rotation = Vector3.zero; + var scale = Vector3.zero; + var instanceName = ""; + foreach (var childInfo in childrenInfo.Children) + { + switch (childInfo.InfoType) + { + case ChildInfoType.AuthRelative: + { + var instance = AuthorityRelativeInstances[childInfo.Id]; + instanceName = instance.name; + position = instance.transform.position; + rotation = instance.transform.eulerAngles; + scale = instance.transform.localScale; + break; + } + case ChildInfoType.AutoSyncOff: + { + instanceName = autoSync.NetworkObjectAutoSyncOffTransforms[(int)childInfo.Id].name; + position = autoSync.NetworkObjectAutoSyncOffTransforms[(int)childInfo.Id].position; + rotation = autoSync.NetworkObjectAutoSyncOffTransforms[(int)childInfo.Id].eulerAngles; + scale = autoSync.NetworkObjectAutoSyncOffTransforms[(int)childInfo.Id].localScale; + break; + } + case ChildInfoType.AutoSyncOn: + { + instanceName = autoSync.NetworkObjectAutoSyncOnTransforms[(int)childInfo.Id].name; + position = autoSync.NetworkObjectAutoSyncOnTransforms[(int)childInfo.Id].position; + rotation = autoSync.NetworkObjectAutoSyncOnTransforms[(int)childInfo.Id].eulerAngles; + scale = autoSync.NetworkObjectAutoSyncOnTransforms[(int)childInfo.Id].localScale; + break; + } + case ChildInfoType.AutoSyncGameObjectOff: + { + instanceName = autoSync.GameObjectAutoSyncOffTransforms[(int)childInfo.Id].name; + position = autoSync.GameObjectAutoSyncOffTransforms[(int)childInfo.Id].position; + rotation = autoSync.GameObjectAutoSyncOffTransforms[(int)childInfo.Id].eulerAngles; + scale = autoSync.GameObjectAutoSyncOffTransforms[(int)childInfo.Id].localScale; + break; + } + case ChildInfoType.AutoSyncGameObjectOn: + { + instanceName = autoSync.GameObjectAutoSyncOnTransforms[(int)childInfo.Id].name; + position = autoSync.GameObjectAutoSyncOnTransforms[(int)childInfo.Id].position; + rotation = autoSync.GameObjectAutoSyncOnTransforms[(int)childInfo.Id].eulerAngles; + scale = autoSync.GameObjectAutoSyncOnTransforms[(int)childInfo.Id].localScale; + break; + } + } + + if (!Approximately(position, childInfo.Position)) + { + Debug.LogWarning($"[{childInfo.InfoType}][{instanceName}][Position Mismatch] Auth: {position} | NonAuth: {childInfo.Position}"); + errorCount++; + } + if (!Approximately(rotation, childInfo.Rotation)) + { + Debug.LogWarning($"[{childInfo.InfoType}][{instanceName}][Rotation Mismatch] Auth: {rotation} | NonAuth: {childInfo.Rotation}"); + errorCount++; + } + if (!Approximately(scale, childInfo.Scale)) + { + Debug.LogWarning($"[{childInfo.InfoType}][{instanceName}][Scale Mismatch] Auth: {scale} | NonAuth: {childInfo.Scale}"); + errorCount++; + } + } + + Debug.Log($"Finished checking children with ({errorCount}) mismatch errors."); + } + + protected bool Approximately(Vector3 a, Vector3 b) + { + var deltaVariance = 0.0125f; + return Math.Round(Mathf.Abs(a.x - b.x), 2) <= deltaVariance && + Math.Round(Mathf.Abs(a.y - b.y), 2) <= deltaVariance && + Math.Round(Mathf.Abs(a.z - b.z), 2) <= deltaVariance; + } + } + + public struct ChildrenInfo : INetworkSerializable + { + public List Children; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + var count = 0; + if (serializer.IsWriter) + { + count = Children.Count; + } + serializer.SerializeValue(ref count); + if (serializer.IsReader) + { + Children = new List(count); + } + + for (int i = 0; i < count; i++) + { + var childInfo = new ChildInfo(); + if (serializer.IsWriter) + { + childInfo = Children[i]; + } + serializer.SerializeValue(ref childInfo); + if (serializer.IsReader) + { + Children.Add(childInfo); + } + } + } + } + + public enum ChildInfoType + { + AuthRelative, + AutoSyncOff, + AutoSyncOn, + AutoSyncGameObjectOff, + AutoSyncGameObjectOn, + } + + public struct ChildInfo : INetworkSerializable + { + public ChildInfoType InfoType; + public ulong Id; + public Vector3 Position; + public Vector3 Rotation; + public Vector3 Scale; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref InfoType); + serializer.SerializeValue(ref Id); + serializer.SerializeValue(ref Position); + serializer.SerializeValue(ref Rotation); + serializer.SerializeValue(ref Scale); } } } diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs new file mode 100644 index 0000000000..777c379c44 --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using Unity.Netcode; +using UnityEngine; + + +namespace TestProject.ManualTests +{ + /// + /// Helper class that builds 4 transform lists based on parent-child hierarchy + /// for the + /// + public class ParentingAutoSyncManager : NetworkBehaviour + { + public static ParentingAutoSyncManager ServerInstance; + public static Dictionary ClientInstances = new Dictionary(); + + public GameObject WithNetworkObjectAutoSyncOn; + public GameObject WithNetworkObjectAutoSyncOff; + public GameObject GameObjectAutoSyncOn; + public GameObject GameObjectAutoSyncOff; + + public List NetworkObjectAutoSyncOnTransforms = new List(); + public List NetworkObjectAutoSyncOffTransforms = new List(); + public List GameObjectAutoSyncOnTransforms = new List(); + public List GameObjectAutoSyncOffTransforms = new List(); + + public static void Reset() + { + ServerInstance = null; + ClientInstances.Clear(); + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + ServerInstance = this; + } + else + { + ClientInstances.Add(NetworkManager.LocalClientId, this); + } + var currentRoot = WithNetworkObjectAutoSyncOn.transform; + NetworkObjectAutoSyncOnTransforms.Add(currentRoot); + NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0)); + NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + + currentRoot = WithNetworkObjectAutoSyncOff.transform; + NetworkObjectAutoSyncOffTransforms.Add(currentRoot); + NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0)); + NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + + currentRoot = GameObjectAutoSyncOn.transform; + GameObjectAutoSyncOnTransforms.Add(currentRoot); + GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0)); + GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + + currentRoot = GameObjectAutoSyncOff.transform; + GameObjectAutoSyncOffTransforms.Add(currentRoot); + GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0)); + GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0)); + } + } +} diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs.meta b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs.meta new file mode 100644 index 0000000000..b8ed6304f2 --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6cb9810cec78d7e4ca96c44f73bbbcf1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs new file mode 100644 index 0000000000..cea4f497e4 --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs @@ -0,0 +1,13 @@ +using Unity.Netcode; +namespace TestProject.RuntimeTests +{ + public class ReparentingCubeNetBhv : NetworkBehaviour + { + public NetworkObject ParentNetworkObject { get; private set; } + + public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) + { + ParentNetworkObject = parentNetworkObject; + } + } +} diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs.meta b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs.meta new file mode 100644 index 0000000000..a96bfea683 --- /dev/null +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ReparentingCubeNetBhv.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ca38a7c14d657e4fa7a2d73b4991ff6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs index badeb189ff..7898adb47b 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs @@ -36,15 +36,6 @@ public override void OnNetworkSpawn() m_Rotate = m_Animator.GetBool("Rotate"); } - private bool HasAuthority() - { - if (IsOwnerAuthority() || IsServerAuthority()) - { - return true; - } - return false; - } - private bool IsServerAuthority() { if (IsServer && m_IsServerAuthoritative) diff --git a/testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab b/testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab new file mode 100644 index 0000000000..38898ce5f7 --- /dev/null +++ b/testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab @@ -0,0 +1,84 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4575260563898367230 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8312435577503810917} + - component: {fileID: 2081691940845411492} + - component: {fileID: 6320789816902190951} + - component: {fileID: 8719147349821933225} + m_Layer: 0 + m_Name: InSceneDefined + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8312435577503810917 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4575260563898367230} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2081691940845411492 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4575260563898367230} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2100153195 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + DistributeOwnership: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &6320789816902190951 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4575260563898367230} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a915cfb2e4f748e4f9526a8bf5ee84f2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &8719147349821933225 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4575260563898367230} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 75dd2043257d8e049acf958cacdf1738, type: 3} + m_Name: + m_EditorClassIdentifier: + IsInSceneDefined: 1 diff --git a/testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab.meta b/testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab.meta new file mode 100644 index 0000000000..86c4063206 --- /dev/null +++ b/testproject/Assets/Tests/Manual/PrefabTestAssets/InSceneDefined.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4a05b9aab67281141953cf6878506d5c +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabListTestAsset.asset b/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabListTestAsset.asset index 1fc372ba10..65c5b6cc7f 100644 --- a/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabListTestAsset.asset +++ b/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabListTestAsset.asset @@ -36,3 +36,11 @@ MonoBehaviour: SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 5911443254905569495, guid: 1e7fa5aea121ba0459459b497c2f03e8, type: 3} + - Override: 0 + Prefab: {fileID: 4575260563898367230, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + SourcePrefabToOverride: {fileID: 6994329648695279756, guid: 5c4a10e01b6668147ba4ccda6a584181, + type: 3} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 5911443254905569495, guid: 1e7fa5aea121ba0459459b497c2f03e8, + type: 3} diff --git a/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabTestScene.unity b/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabTestScene.unity index 758e034f18..a6204f5469 100644 --- a/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabTestScene.unity +++ b/testproject/Assets/Tests/Manual/PrefabTestAssets/PrefabTestScene.unity @@ -459,86 +459,6 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} ---- !u!1 &910084391 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 910084393} - - component: {fileID: 910084392} - - component: {fileID: 910084395} - - component: {fileID: 910084394} - m_Layer: 0 - m_Name: InSceneDefined - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &910084392 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 910084391} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: - GlobalObjectIdHash: 3666358976 - InScenePlacedSourceGlobalObjectIdHash: 0 - AlwaysReplicateAsRoot: 0 - SynchronizeTransform: 1 - ActiveSceneSynchronization: 0 - SceneMigrationSynchronization: 1 - SpawnWithObservers: 1 - DontDestroyWithOwner: 0 - AutoObjectParentSync: 1 ---- !u!4 &910084393 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 910084391} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &910084394 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 910084391} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 75dd2043257d8e049acf958cacdf1738, type: 3} - m_Name: - m_EditorClassIdentifier: - IsInSceneDefined: 1 ---- !u!114 &910084395 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 910084391} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: a915cfb2e4f748e4f9526a8bf5ee84f2, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!1001 &1713911584 PrefabInstance: m_ObjectHideFlags: 0 @@ -743,6 +663,84 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0} +--- !u!1001 &909918602607054652 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 2081691940845411492, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: GlobalObjectIdHash + value: 3537691690 + objectReference: {fileID: 0} + - target: {fileID: 2081691940845411492, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 2100153195 + objectReference: {fileID: 0} + - target: {fileID: 4575260563898367230, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_Name + value: InSceneDefined + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8312435577503810917, guid: 4a05b9aab67281141953cf6878506d5c, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 4a05b9aab67281141953cf6878506d5c, type: 3} --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -753,4 +751,4 @@ SceneRoots: - {fileID: 826833804} - {fileID: 1713911584} - {fileID: 557098469} - - {fileID: 910084393} + - {fileID: 909918602607054652} diff --git a/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs b/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs index 1146c1969e..1271f70f72 100644 --- a/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs +++ b/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs @@ -176,7 +176,9 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } private const int k_StatesToLog = 80; - private bool m_StopLoggingStates = false; +#if DEBUG_NETWORKTRANSFORM || UNITY_INCLUDE_TESTS + private bool m_StopLoggingStates; +#endif private Dictionary>> m_FirstInitialStateUpdates = new Dictionary>>(); private void InternalAddLogEntry(ref NetworkTransformState networkTransformState, ulong targetClient, bool preUpdate = false) @@ -219,7 +221,11 @@ private void InternalAddLogEntry(ref NetworkTransformState networkTransformState ownerTable[localClientId].Add(state); if (ownerTable[localClientId].Count >= m_StatesToLog) { - m_StopLoggingStates = true; + if (DebugTransform && !m_StopLoggingStates) + { + m_StopLoggingStates = true; + } + if (IsServer) { LogInitialTransformStates(localClientId, ownerId); diff --git a/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs b/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs index 699f0ed47e..8343b9d7f2 100644 --- a/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs +++ b/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs @@ -7,20 +7,41 @@ public class ParentPlayerToInSceneNetworkObject : NetworkBehaviour { public override void OnNetworkSpawn() { - if (IsServer && NetworkManager.IsServer) + if (IsServer && IsOwner) { - // Server subscribes to the NetworkSceneManager.OnSceneEvent event - NetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - +#if NGO_DAMODE + // Client-Server mode, the server handles parenting the players + if (!NetworkManager.DistributedAuthorityMode) + { + NetworkManager.OnClientConnectedCallback += NetworkManager_OnClientConnectedCallback; + } +#endif // Server player is parented under this NetworkObject SetPlayerParent(NetworkManager.LocalClientId); } } + private void NetworkManager_OnClientConnectedCallback(ulong clientId) + { + if (clientId != NetworkManager.LocalClientId) + { + // Set the newly joined and synchronized client-player as a child of this in-scene placed NetworkObject + SetPlayerParent(clientId); + } + } + private void SetPlayerParent(ulong clientId) { - if (IsSpawned && IsServer) + if (IsSpawned) { +#if NGO_DAMODE + var playerObject = NetworkManager.SpawnManager.GetPlayerNetworkObject(clientId); + if (playerObject.gameObject.scene != gameObject.scene) + { + SceneManager.MoveGameObjectToScene(playerObject.gameObject, gameObject.scene); + } + playerObject.TrySetParent(NetworkObject, false); +#else // As long as the client (player) is in the connected clients list if (NetworkManager.ConnectedClients.ContainsKey(clientId)) { @@ -33,9 +54,18 @@ private void SetPlayerParent(ulong clientId) // We parent in local space by setting the WorldPositionStays value to false NetworkManager.ConnectedClients[clientId].PlayerObject.TrySetParent(NetworkObject, false); } +#endif } } +#if NGO_DAMODE + [ServerRpc(RequireOwnership = false)] + private void SetParentServerRpc() + { + SetPlayerParent(NetworkManager.LocalClientId); + } +#endif + private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) { // OnSceneEvent is very useful for many things diff --git a/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs b/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs index c5099e6a25..5811e6aeac 100644 --- a/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs +++ b/testproject/Assets/Tests/Manual/Scripts/PlayerMovementManager.cs @@ -25,7 +25,7 @@ private void Update() return; } - if (m_RandomMovement.HasAuthority()) + if (m_RandomMovement.IsAuthority()) { if (Input.GetKeyDown(KeyCode.Space) && IsOwner) { diff --git a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs index 9b6eaaf71c..c54e908458 100644 --- a/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs +++ b/testproject/Assets/Tests/Manual/Scripts/RandomMovement.cs @@ -16,7 +16,7 @@ public class RandomMovement : NetworkBehaviour, IPlayerMovement private NetworkTransform m_NetworkTransform; private ClientNetworkTransform m_ClientNetworkTransform; - public bool HasAuthority() + public bool IsAuthority() { if (m_NetworkTransform != null) { @@ -38,7 +38,7 @@ public override void OnNetworkSpawn() m_NetworkTransform = GetComponent(); if (NetworkObject != null && m_Rigidbody != null) { - if (HasAuthority()) + if (IsAuthority()) { ChangeDirection(true, true); } @@ -61,7 +61,7 @@ private void FixedUpdate() { return; } - if (HasAuthority()) + if (IsAuthority()) { if (m_Rigidbody == null) { @@ -81,7 +81,7 @@ private void FixedUpdate() private void OnCollisionStay(Collision collision) { - if (HasAuthority()) + if (IsAuthority()) { if (collision.gameObject.CompareTag("Floor") || collision.gameObject.CompareTag("GenericObject")) { diff --git a/testproject/Assets/Tests/Runtime/AddressablesTests.cs b/testproject/Assets/Tests/Runtime/AddressablesTests.cs index 7fa0782437..1d0f5824dc 100644 --- a/testproject/Assets/Tests/Runtime/AddressablesTests.cs +++ b/testproject/Assets/Tests/Runtime/AddressablesTests.cs @@ -12,8 +12,11 @@ namespace TestProject.RuntimeTests { - [TestFixture(HostOrServer.Server)] +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] public class AddressablesTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 8061ce8de4..f68e05916d 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -19,8 +19,17 @@ namespace TestProject.RuntimeTests /// Possibly we could build this at runtime, but for now it uses the same animator controller as the manual /// test does. /// - [TestFixture(HostOrServer.Host)] - [TestFixture(HostOrServer.Server)] +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost, OwnerShipMode.ClientOwner, AuthoritativeMode.OwnerAuth)] +#endif + [TestFixture(HostOrServer.Host, OwnerShipMode.ServerOwner, AuthoritativeMode.OwnerAuth)] + [TestFixture(HostOrServer.Host, OwnerShipMode.ServerOwner, AuthoritativeMode.ServerAuth)] + [TestFixture(HostOrServer.Host, OwnerShipMode.ClientOwner, AuthoritativeMode.OwnerAuth)] + [TestFixture(HostOrServer.Host, OwnerShipMode.ClientOwner, AuthoritativeMode.ServerAuth)] + [TestFixture(HostOrServer.Server, OwnerShipMode.ServerOwner, AuthoritativeMode.OwnerAuth)] + [TestFixture(HostOrServer.Server, OwnerShipMode.ServerOwner, AuthoritativeMode.ServerAuth)] + [TestFixture(HostOrServer.Server, OwnerShipMode.ClientOwner, AuthoritativeMode.OwnerAuth)] + [TestFixture(HostOrServer.Server, OwnerShipMode.ClientOwner, AuthoritativeMode.ServerAuth)] public class NetworkAnimatorTests : NetcodeIntegrationTest { private const string k_AnimatorObjectName = "AnimatorObject"; @@ -37,10 +46,15 @@ public class NetworkAnimatorTests : NetcodeIntegrationTest private AnimatorTestHelper.ParameterValues m_ParameterValues; private Object m_AnimatorObjectPrefab; private Object m_OwnerAnimatorObjectPrefab; + private OwnerShipMode m_OwnerShipMode; + private AuthoritativeMode m_AuthoritativeMode; - public NetworkAnimatorTests(HostOrServer hostOrServer) + + public NetworkAnimatorTests(HostOrServer hostOrServer, OwnerShipMode ownerShipMode, AuthoritativeMode authoritative) { m_UseHost = hostOrServer == HostOrServer.Host; + m_OwnerShipMode = ownerShipMode; + m_AuthoritativeMode = authoritative; } protected override void OnOneTimeSetup() @@ -153,27 +167,27 @@ private string GetNetworkAnimatorName(AuthoritativeMode authoritativeMode) /// /// Server or Owner authoritative [Test] - public void ParameterUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + public void ParameterUpdateTests() { - VerboseDebug($" ++++++++++++++++++ Parameter Test [{ownerShipMode}] Starting ++++++++++++++++++ "); + VerboseDebug($" ++++++++++++++++++ Parameter Test [{m_OwnerShipMode}] Starting ++++++++++++++++++ "); // Spawn our test animator object - var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); // Wait for it to spawn server-side var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); - Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Wait for it to spawn client-side success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ClientSideInstances.ContainsKey(m_ClientNetworkManagers[0].LocalClientId)); - Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Create new parameter values m_ParameterValues = new AnimatorTestHelper.ParameterValues() { FloatValue = 1.0f, IntValue = 5, BoolValue = true }; - if (authoritativeMode == AuthoritativeMode.OwnerAuth) + if (m_AuthoritativeMode == AuthoritativeMode.OwnerAuth) { - var objectToUpdate = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + var objectToUpdate = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; // Set the new parameter values via the owner objectToUpdate.UpdateParameters(m_ParameterValues); } @@ -184,9 +198,9 @@ public void ParameterUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] } // Wait for the client side to update to the new parameter values - success = WaitForConditionOrTimeOutWithTimeTravel(() => ParameterValuesMatch(ownerShipMode, authoritativeMode, m_EnableVerboseDebug)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ParameterValuesMatch(m_OwnerShipMode, m_AuthoritativeMode, m_EnableVerboseDebug)); Assert.True(success, $"Timed out waiting for the client-side parameters to match {m_ParameterValues}!"); - VerboseDebug($" ------------------ Parameter Test [{ownerShipMode}] Stopping ------------------ "); + VerboseDebug($" ------------------ Parameter Test [{m_OwnerShipMode}] Stopping ------------------ "); } @@ -285,30 +299,30 @@ private bool AllClientsTransitioningAnyState() /// Verifies that cross fading is synchronized with currently connected clients /// [UnityTest] - public IEnumerator CrossFadeTransitionTests([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + public IEnumerator CrossFadeTransitionTests() { CrossFadeTransitionDetect.ResetTest(); CrossFadeTransitionDetect.SetTargetAnimationState(AnimatorTestHelper.TargetCrossFadeState); - VerboseDebug($" ++++++++++++++++++ Cross Fade Transition Test [{ownerShipMode}] Starting ++++++++++++++++++ "); + VerboseDebug($" ++++++++++++++++++ Cross Fade Transition Test [{m_OwnerShipMode}] Starting ++++++++++++++++++ "); CrossFadeTransitionDetect.IsVerboseDebug = m_EnableVerboseDebug; // Spawn our test animator object - var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); // Wait for it to spawn server-side var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); - Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Wait for it to spawn client-side success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); - Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); - var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); + var animatorTestHelper = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; var layerCount = animatorTestHelper.GetAnimator().layerCount; var animationStateCount = animatorTestHelper.GetAnimatorStateCount(); Assert.True(layerCount == animationStateCount, $"AnimationState count {animationStateCount} does not equal the layer count {layerCount}!"); - if (authoritativeMode == AuthoritativeMode.ServerAuth) + if (m_AuthoritativeMode == AuthoritativeMode.ServerAuth) { animatorTestHelper = AnimatorTestHelper.ServerSideInstance; } @@ -332,31 +346,31 @@ public IEnumerator CrossFadeTransitionTests([Values] OwnerShipMode ownerShipMode /// /// Server or Owner authoritative [UnityTest] - public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + public IEnumerator TriggerUpdateTests() { CheckStateEnterCount.ResetTest(); - VerboseDebug($" ++++++++++++++++++ Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); + VerboseDebug($" ++++++++++++++++++ Trigger Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Starting ++++++++++++++++++ "); TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; // Spawn our test animator object - var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); // Wait for it to spawn server-side var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); - Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Wait for it to spawn client-side success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); - Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); - var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); + var animatorTestHelper = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; var layerCount = animatorTestHelper.GetAnimator().layerCount; var animationStateCount = animatorTestHelper.GetAnimatorStateCount(); Assert.True(layerCount == animationStateCount, $"AnimationState count {animationStateCount} does not equal the layer count {layerCount}!"); - if (authoritativeMode == AuthoritativeMode.ServerAuth) + if (m_AuthoritativeMode == AuthoritativeMode.ServerAuth) { animatorTestHelper = AnimatorTestHelper.ServerSideInstance; } @@ -373,12 +387,12 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val animatorTestHelper.SetTrigger(); VerboseDebug($"New Trigger State: {animatorTestHelper.GetCurrentTriggerState()}"); // Wait for all triggers to fire - yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(ownerShipMode), timeOutHelper); + yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(m_OwnerShipMode), timeOutHelper); retryTrigger = timeOutHelper.TimedOut; if (retryTrigger) { count++; - Debug.LogWarning($"[{ownerShipMode}][{count}] Resending trigger!"); + Debug.LogWarning($"[{m_OwnerShipMode}][{count}] Resending trigger!"); } } } @@ -386,7 +400,7 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val { animatorTestHelper.SetTrigger(); // Wait for all triggers to fire - yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(ownerShipMode)); + yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(m_OwnerShipMode)); AssertOnTimeout($"Timed out waiting for all triggers to match!"); } @@ -430,12 +444,12 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val animatorTestHelper.SetTrigger("Attack"); VerboseDebug($"New Trigger State: {animatorTestHelper.GetCurrentTriggerState()}"); // Wait for all triggers to fire - yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(ownerShipMode), timeOutHelper); + yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(m_OwnerShipMode), timeOutHelper); retryTrigger = timeOutHelper.TimedOut; if (retryTrigger) { count++; - Debug.LogWarning($"[{ownerShipMode}][{count}] Resending trigger!"); + Debug.LogWarning($"[{m_OwnerShipMode}][{count}] Resending trigger!"); } } } @@ -445,12 +459,12 @@ public IEnumerator TriggerUpdateTests([Values] OwnerShipMode ownerShipMode, [Val animator.SetInteger("WeaponType", 1); animatorTestHelper.SetTrigger("Attack"); // Wait for all triggers to fire - yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(ownerShipMode)); + yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(m_OwnerShipMode)); AssertOnTimeout($"Timed out waiting for all triggers to match!"); } AnimatorTestHelper.IsTriggerTest = false; - VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); + VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Stopping ------------------ "); } protected override void OnNewClientCreated(NetworkManager networkManager) @@ -466,31 +480,31 @@ protected override void OnNewClientCreated(NetworkManager networkManager) /// /// Server or Owner authoritative [Test] - public void WeightUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + public void WeightUpdateTests() { CheckStateEnterCount.ResetTest(); TriggerTest.ResetTest(); - VerboseDebug($" ++++++++++++++++++ Weight Test [{ownerShipMode}] Starting ++++++++++++++++++ "); + VerboseDebug($" ++++++++++++++++++ Weight Test [{m_OwnerShipMode}] Starting ++++++++++++++++++ "); TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; // Spawn our test animator object - var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); // Wait for it to spawn server-side var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); - Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Wait for it to spawn client-side success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); - Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); - var animatorTestHelper = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); + var animatorTestHelper = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; var layerCount = animatorTestHelper.GetAnimator().layerCount; var animationStateCount = animatorTestHelper.GetAnimatorStateCount(); Assert.True(layerCount == animationStateCount, $"AnimationState count {animationStateCount} does not equal the layer count {layerCount}!"); - if (authoritativeMode == AuthoritativeMode.ServerAuth) + if (m_AuthoritativeMode == AuthoritativeMode.ServerAuth) { animatorTestHelper = AnimatorTestHelper.ServerSideInstance; } @@ -499,12 +513,12 @@ public void WeightUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] Aut animatorTestHelper.SetLayerWeight(1, 0.75f); // Wait for all instances to update their weight value for layer 1 - success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(ownerShipMode, 1, 0.75f)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(m_OwnerShipMode, 1, 0.75f)); Assert.True(success, $"Timed out waiting for all instances to match weight 0.75 on layer 1!"); animatorTestHelper.SetLayerWeight(1, originalWeight); // Wait for all instances to update their weight value for layer 1 - success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(ownerShipMode, 1, originalWeight)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(m_OwnerShipMode, 1, originalWeight)); Assert.True(success, $"Timed out waiting for all instances to match weight {originalWeight} on layer 1!"); // Now set the layer weight to 0 @@ -514,16 +528,16 @@ public void WeightUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] Aut CreateAndStartNewClientWithTimeTravel(); // Verify the late joined client is synchronized to the changed weight - success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(ownerShipMode, 1, 0.0f)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(m_OwnerShipMode, 1, 0.0f)); Assert.True(success, $"[Late-Join] Timed out waiting for all instances to match weight 0 on layer 1!"); animatorTestHelper.SetLayerWeight(1, originalWeight); // Wait for all instances to update their weight value for layer 1 - success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(ownerShipMode, 1, originalWeight)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesSameLayerWeight(m_OwnerShipMode, 1, originalWeight)); Assert.True(success, $"Timed out waiting for all instances to match weight {originalWeight} on layer 1!"); AnimatorTestHelper.IsTriggerTest = false; - VerboseDebug($" ------------------ Weight Test [{ownerShipMode}] Stopping ------------------ "); + VerboseDebug($" ------------------ Weight Test [{m_OwnerShipMode}] Stopping ------------------ "); } /// @@ -532,29 +546,29 @@ public void WeightUpdateTests([Values] OwnerShipMode ownerShipMode, [Values] Aut /// /// Server or Owner authoritative [UnityTest] - public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + public IEnumerator LateJoinTriggerSynchronizationTest() { - VerboseDebug($" ++++++++++++++++++ Late Join Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); + VerboseDebug($" ++++++++++++++++++ Late Join Trigger Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Starting ++++++++++++++++++ "); TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; CheckStateEnterCount.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; - bool isClientOwner = ownerShipMode == OwnerShipMode.ClientOwner; + bool isClientOwner = m_OwnerShipMode == OwnerShipMode.ClientOwner; // Spawn our test animator object - var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); // Wait for it to spawn server-side var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); - Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Wait for it to spawn client-side success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); - Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Set the trigger based on the type of test - if (authoritativeMode == AuthoritativeMode.OwnerAuth) + if (m_AuthoritativeMode == AuthoritativeMode.OwnerAuth) { - var objectToUpdate = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + var objectToUpdate = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; // Set the animation trigger via the owner objectToUpdate.SetTrigger(); } @@ -565,15 +579,15 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own } // Wait for all triggers to fire - yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(ownerShipMode)); + yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(m_OwnerShipMode)); AssertOnTimeout($"Timed out waiting for all triggers to match!"); // Create new parameter values m_ParameterValues = new AnimatorTestHelper.ParameterValues() { FloatValue = 1.0f, IntValue = 5, BoolValue = true }; - if (authoritativeMode == AuthoritativeMode.OwnerAuth) + if (m_AuthoritativeMode == AuthoritativeMode.OwnerAuth) { - var objectToUpdate = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + var objectToUpdate = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; // Set the new parameter values objectToUpdate.UpdateParameters(m_ParameterValues); } @@ -584,7 +598,7 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own } // Wait for the client side to update to the new parameter values - success = WaitForConditionOrTimeOutWithTimeTravel(() => ParameterValuesMatch(ownerShipMode, authoritativeMode, m_EnableVerboseDebug)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ParameterValuesMatch(m_OwnerShipMode, m_AuthoritativeMode, m_EnableVerboseDebug)); Assert.True(success, $"Timed out waiting for the client-side parameters to match {m_ParameterValues.ValuesToString()}!"); CreateAndStartNewClientWithTimeTravel(); @@ -593,14 +607,14 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own // Wait for it to spawn client-side success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); - Assert.True(success, $"Timed out waiting for the late joining client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the late joining client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Make sure the AnimatorTestHelper client side instances is the same as the TotalClients var calculatedClients = (AnimatorTestHelper.ClientSideInstances.Count + (m_UseHost ? 1 : 0)); Assert.True(calculatedClients == TotalClients, $"Number of client"); // Now check that the late joining client and all other clients are synchronized to the trigger - yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(ownerShipMode)); + yield return WaitForConditionOrTimeOut(() => AllTriggersDetected(m_OwnerShipMode)); var message = string.Empty; if (s_GlobalTimeoutHelper.TimedOut) @@ -613,12 +627,12 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own } AssertOnTimeout($"Timed out waiting for the late joining client's triggers to match!{message}", s_GlobalTimeoutHelper); // Now check that the late joining client and all other clients are synchronized to the updated parameter values - success = WaitForConditionOrTimeOutWithTimeTravel(() => ParameterValuesMatch(ownerShipMode, authoritativeMode, m_EnableVerboseDebug)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ParameterValuesMatch(m_OwnerShipMode, m_AuthoritativeMode, m_EnableVerboseDebug)); Assert.True(success, $"Timed out waiting for the client-side parameters to match {m_ParameterValues.ValuesToString()}!"); var newlyJoinedClient = m_ClientNetworkManagers[NumberOfClients]; StopOneClientWithTimeTravel(newlyJoinedClient); - VerboseDebug($" ------------------ Late Join Trigger Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); + VerboseDebug($" ------------------ Late Join Trigger Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Stopping ------------------ "); } /// @@ -627,30 +641,30 @@ public IEnumerator LateJoinTriggerSynchronizationTest([Values] OwnerShipMode own /// /// Server or Owner authoritative [UnityTest] - public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipMode, [Values] AuthoritativeMode authoritativeMode) + public IEnumerator LateJoinSynchronizationTest() { - VerboseDebug($" ++++++++++++++++++ Late Join Synchronization Test [{TriggerTest.Iteration}][{ownerShipMode}] Starting ++++++++++++++++++ "); + VerboseDebug($" ++++++++++++++++++ Late Join Synchronization Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Starting ++++++++++++++++++ "); StateSyncTest.IsVerboseDebug = m_EnableVerboseDebug; TriggerTest.IsVerboseDebug = m_EnableVerboseDebug; AnimatorTestHelper.IsTriggerTest = m_EnableVerboseDebug; - bool isClientOwner = ownerShipMode == OwnerShipMode.ClientOwner; + bool isClientOwner = m_OwnerShipMode == OwnerShipMode.ClientOwner; // Spawn our test animator object - var objectInstance = SpawnPrefab(ownerShipMode == OwnerShipMode.ClientOwner, authoritativeMode); + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); // Wait for it to spawn server-side var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); - Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Wait for it to spawn client-side success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); - Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Set the late join parameter based on the type of test - if (authoritativeMode == AuthoritativeMode.OwnerAuth) + if (m_AuthoritativeMode == AuthoritativeMode.OwnerAuth) { - var objectToUpdate = ownerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + var objectToUpdate = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; // Set the late join parameter via the owner objectToUpdate.SetLateJoinParam(true); } @@ -675,7 +689,7 @@ public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipM // Wait for the client to have spawned and the spawned prefab to be instantiated success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); - Assert.True(success, $"Timed out waiting for the late joining client-side instance of {GetNetworkAnimatorName(authoritativeMode)} to be spawned!"); + Assert.True(success, $"Timed out waiting for the late joining client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); // Make sure the AnimatorTestHelper client side instances is the same as the TotalClients var calculatedClients = (AnimatorTestHelper.ClientSideInstances.Count + (m_UseHost ? 1 : 0)); @@ -691,7 +705,7 @@ public IEnumerator LateJoinSynchronizationTest([Values] OwnerShipMode ownerShipM var newlyJoinedClient = m_ClientNetworkManagers[NumberOfClients]; StopOneClientWithTimeTravel(newlyJoinedClient); - VerboseDebug($" ------------------ Late Join Synchronization Test [{TriggerTest.Iteration}][{ownerShipMode}] Stopping ------------------ "); + VerboseDebug($" ------------------ Late Join Synchronization Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Stopping ------------------ "); } /// diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab index b5fb402cf2..8a8579974e 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab @@ -26,14 +26,15 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 198001549274419308} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -305, y: -240, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 3456315205079358758} - {fileID: 8794063308444682358} m_Father: {fileID: 9223070337736602118} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &164258606156585344 MeshFilter: @@ -54,6 +55,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -92,9 +94,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 198001549274419308} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1.0000002, y: 1, z: 1.0000002} m_Center: {x: 0.0000019073493, y: 0.000000007450581, z: -0.000001907349} --- !u!1 &5279449370783340031 @@ -123,12 +133,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5279449370783340031} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0.505} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &2013809210325159650 MeshFilter: @@ -149,6 +160,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -187,9 +199,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5279449370783340031} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &5679353742332220714 @@ -218,12 +238,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5679353742332220714} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -0.514} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &1796761232470876384 MeshFilter: @@ -244,6 +265,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -282,9 +304,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5679353742332220714} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &9223070337736602117 @@ -315,13 +345,14 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 9223070337736602117} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 320, y: 240, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 5566730610036911080} m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &9223070337736602119 MonoBehaviour: @@ -335,16 +366,20 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 951099334 + GlobalObjectIdHash: 1958757820 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 ActiveSceneSynchronization: 0 SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!95 &6515743261518512780 Animator: - serializedVersion: 3 + serializedVersion: 5 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -357,10 +392,12 @@ Animator: m_UpdateMode: 0 m_ApplyRootMotion: 0 m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 m_WarningMessage: m_HasTransformHierarchy: 1 m_AllowConstantClipSamplingOptimization: 1 - m_KeepAnimatorControllerStateOnDisable: 0 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 --- !u!114 &3276706183666593358 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab index a5fe03a9af..da9b866682 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab @@ -26,14 +26,15 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 198001549274419308} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -305, y: -240, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 3456315205079358758} - {fileID: 8794063308444682358} m_Father: {fileID: 9223070337736602118} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &164258606156585344 MeshFilter: @@ -54,6 +55,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -92,9 +94,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 198001549274419308} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1.0000002, y: 1, z: 1.0000002} m_Center: {x: 0.0000019073493, y: 0.000000007450581, z: -0.000001907349} --- !u!1 &5279449370783340031 @@ -123,12 +133,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5279449370783340031} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0.505} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &2013809210325159650 MeshFilter: @@ -149,6 +160,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -187,9 +199,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5279449370783340031} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &5679353742332220714 @@ -218,12 +238,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5679353742332220714} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -0.514} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5566730610036911080} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &1796761232470876384 MeshFilter: @@ -244,6 +265,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -282,9 +304,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5679353742332220714} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &9223070337736602117 @@ -315,13 +345,14 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 9223070337736602117} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 320, y: 240, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 5566730610036911080} m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &9223070337736602119 MonoBehaviour: @@ -335,16 +366,20 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 951099334 + GlobalObjectIdHash: 3978410502 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 ActiveSceneSynchronization: 0 SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!95 &6515743261518512780 Animator: - serializedVersion: 3 + serializedVersion: 5 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -357,10 +392,12 @@ Animator: m_UpdateMode: 0 m_ApplyRootMotion: 0 m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 m_WarningMessage: m_HasTransformHierarchy: 1 m_AllowConstantClipSamplingOptimization: 1 - m_KeepAnimatorControllerStateOnDisable: 0 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 --- !u!114 &-3200389182615764163 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs index 7ee9a4ecef..9cb93d6efa 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs @@ -9,6 +9,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class ClientSynchronizationValidationTest : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -21,6 +25,9 @@ public class ClientSynchronizationValidationTest : NetcodeIntegrationTest private List m_ClientSceneVerifiers = new List(); +#if NGO_DAMODE + public ClientSynchronizationValidationTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif protected override void OnNewClientStarted(NetworkManager networkManager) { if (m_IncludeSceneVerificationHandler) diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index fa265302f9..3d4f467946 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -12,6 +12,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class InScenePlacedNetworkObjectTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -20,6 +24,9 @@ public class InScenePlacedNetworkObjectTests : NetcodeIntegrationTest private Scene m_ServerSideSceneLoaded; private bool m_CanStartServerAndClients; +#if NGO_DAMODE + public InScenePlacedNetworkObjectTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif protected override IEnumerator OnSetup() { NetworkObjectTestComponent.Reset(); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs index b44c670ffa..c403e96bbd 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs @@ -73,7 +73,11 @@ public override void OnNetworkDespawn() { OnInSceneObjectDespawned?.Invoke(NetworkObject); m_HasNotifiedSpawned = false; - Debug.Log($"{NetworkManager.name} de-spawned {gameObject.name}."); + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"{NetworkManager.name} de-spawned {gameObject.name}."); + } + SpawnedInstances.Remove(this); if (DisableOnDespawn) { @@ -89,7 +93,10 @@ private void Update() // We do this so the ObjectNameIdentifier has a chance to label it properly if (IsSpawned && !m_HasNotifiedSpawned) { - Debug.Log($"{NetworkManager.name} spawned {gameObject.name} with scene origin handle {gameObject.scene.handle}."); + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + Debug.Log($"{NetworkManager.name} spawned {gameObject.name} with scene origin handle {gameObject.scene.handle}."); + } m_HasNotifiedSpawned = true; } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs index f46292d90e..c3fefb36ea 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs @@ -10,6 +10,9 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkSceneManagerEventCallbacks : NetcodeIntegrationTest diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs index 90d5a34934..204fdd28b5 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs @@ -10,6 +10,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost, LoadSceneMode.Single)] + [TestFixture(HostOrServer.DAHost, LoadSceneMode.Additive)] +#endif [TestFixture(HostOrServer.Host, LoadSceneMode.Single)] [TestFixture(HostOrServer.Host, LoadSceneMode.Additive)] [TestFixture(HostOrServer.Server, LoadSceneMode.Single)] diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs index 2c29c981bb..5afba0af57 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs @@ -10,6 +10,9 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkSceneManagerEventNotifications : NetcodeIntegrationTest @@ -262,7 +265,7 @@ public IEnumerator SceneLoadingAndNotifications([Values] LoadSceneMode loadScene AssertOnTimeout($"Timed out waiting for all clients to switch to scene {m_CurrentSceneName}!"); - // Now single mode load a a new scene (i.e. "scene switch") + // Now single mode load a new scene (i.e. "scene switch") m_CurrentSceneName = k_BaseUnitTestSceneName; ResetWait(); Assert.AreEqual(m_ServerNetworkManager.SceneManager.LoadScene(m_CurrentSceneName, loadSceneMode), SceneEventProgressStatus.Started); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs index 1da238adec..e6c5f550bb 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs @@ -1,3 +1,5 @@ +// TODO: Rewrite test to use the tools package. Debug simulator not available in UTP 2.X. +#if !UTP_TRANSPORT_2_0_ABOVE using System.Collections; using System.Collections.Generic; using NUnit.Framework; @@ -9,9 +11,6 @@ using UnityEngine.TestTools; using Object = UnityEngine.Object; -// TODO: Rewrite test to use the tools package. Debug simulator not available in UTP 2.X. -#if !UTP_TRANSPORT_2_0_ABOVE - namespace TestProject.RuntimeTests { /// diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs index f48f869e9a..6243dec581 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs @@ -10,6 +10,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -17,6 +21,10 @@ public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest protected Dictionary m_InSceneObjectList = new Dictionary(); +#if NGO_DAMODE + public NetworkSceneManagerPopulateInSceneTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + protected override IEnumerator OnSetup() { m_InSceneObjectList.Clear(); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs index 680dfc3395..cbdc02ef33 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs @@ -9,6 +9,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost, LoadSceneMode.Single)] + [TestFixture(HostOrServer.DAHost, LoadSceneMode.Additive)] +#endif [TestFixture(HostOrServer.Host, LoadSceneMode.Single)] [TestFixture(HostOrServer.Host, LoadSceneMode.Additive)] [TestFixture(HostOrServer.Server, LoadSceneMode.Single)] diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs index 138fa9b4cb..dbc9a4cb41 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs @@ -7,6 +7,9 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkSceneManagerUsageTests : NetcodeIntegrationTest @@ -65,7 +68,15 @@ public IEnumerator ClientCannotUseException([Values(LoadSceneMode.Single, LoadSc { m_CurrentSceneName = k_AdditiveScene1; var statusResult = m_ClientNetworkManagers[0].SceneManager.LoadScene(m_CurrentSceneName, loadSceneMode); - Assert.True(statusResult == SceneEventProgressStatus.ServerOnlyAction, $"[Client][Load][{loadSceneMode}] Failed to receive a {nameof(SceneEventProgressStatus.ServerOnlyAction)} response!"); + var expectedResult = SceneEventProgressStatus.ServerOnlyAction; +#if NGO_DAMODE + if (m_DistributedAuthority) + { + expectedResult = SceneEventProgressStatus.SessionOwnerOnlyAction; + } +#endif + + Assert.True(statusResult == expectedResult, $"[Client][Load][{loadSceneMode}] Failed to receive a {nameof(expectedResult)} response!"); // Check that a client cannot call UnloadScene m_ServerNetworkManager.SceneManager.OnSceneEvent += ServerSceneManager_OnSceneEvent; @@ -82,7 +93,9 @@ public IEnumerator ClientCannotUseException([Values(LoadSceneMode.Single, LoadSc // Now try to unload the scene as a client statusResult = m_ClientNetworkManagers[0].SceneManager.UnloadScene(m_CurrentScene); - Assert.True(statusResult == SceneEventProgressStatus.ServerOnlyAction, $"[Client][Unload] Failed to receive a {nameof(SceneEventProgressStatus.ServerOnlyAction)} response!"); + + + Assert.True(statusResult == expectedResult, $"[Client][Unload] Failed to receive a {nameof(expectedResult)} response!"); foreach (var clientNetworkManager in m_ClientNetworkManagers) { diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs index ac9a78514e..001cba5c94 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs @@ -10,6 +10,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class SceneEventProgressTests : NetcodeIntegrationTest { private const string k_SceneUsedToGetAsyncOperation = "EmptyScene"; @@ -23,6 +27,10 @@ public class SceneEventProgressTests : NetcodeIntegrationTest private List m_ClientThatShouldNotHaveCompleted = new List(); private List m_ClientThatShouldHaveCompleted = new List(); +#if NGO_DAMODE + public SceneEventProgressTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + private bool SceneEventProgressComplete(SceneEventProgress sceneEventProgress) { m_SceneEventProgressCompleted = true; diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 77d8403d57..03c55887e7 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -181,11 +181,6 @@ protected override float GetDeltaVarianceThreshold() private StringBuilder m_ValidationErrors; - private string GetVector3Values(ref Vector3 vector3) - { - return $"({vector3.x:F6},{vector3.y:F6},{vector3.z:F6})"; - } - /// /// Validates all transform instance values match the authority's /// diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting.meta b/testproject/Assets/Tests/Runtime/ObjectParenting.meta index 63a52b767a..9d1e1001b8 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4b0e72312f6c8404891c5cb3f2a3f36d +guid: f81ead0033298b049a68c12c6d68659c folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/BlueColMat.mat.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/BlueColMat.mat.meta index 6df17a3b9b..802349a9df 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/BlueColMat.mat.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/BlueColMat.mat.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 52c34e1beacd44c9bb0f6375c891b4cf +guid: cb2191cfdbf1c9548b6d2705cd1598b3 NativeFormatImporter: externalObjects: {} mainObjectFileID: 2100000 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/GrayColMat.mat.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/GrayColMat.mat.meta index 35533b8924..9406e30bdc 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/GrayColMat.mat.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/GrayColMat.mat.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 03e094ab168544996a7d1d6dcdc60058 +guid: 0e597626cbe10b140ab0de9400e6bcaa NativeFormatImporter: externalObjects: {} mainObjectFileID: 2100000 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/GreenColMat.mat.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/GreenColMat.mat.meta index 6cc05ba8f0..bef9108007 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/GreenColMat.mat.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/GreenColMat.mat.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d8e0487da5b3d4659909d18d138cff5a +guid: 08e0c61b5a83bfb46a037cbecbebc671 NativeFormatImporter: externalObjects: {} mainObjectFileID: 2100000 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs index 1b1257a6c3..6d8b99a7fa 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs @@ -10,6 +10,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class NetworkObjectParentingTests { private const int k_ClientInstanceCount = 1; @@ -30,6 +34,14 @@ public class NetworkObjectParentingTests private Scene m_InitScene; private Scene m_TestScene; +#if NGO_DAMODE + private SessionModeTypes m_SessionModeType; + public NetworkObjectParentingTests(SessionModeTypes sessionModeType) + { + m_SessionModeType = sessionModeType; + } +#endif + private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (scene.name == nameof(NetworkObjectParentingTests)) @@ -63,6 +75,16 @@ public IEnumerator Setup() Assert.That(m_ClientNetworkManagers, Is.Not.Null); Assert.That(m_ClientNetworkManagers.Length, Is.EqualTo(k_ClientInstanceCount)); +#if NGO_DAMODE + m_ServerNetworkManager.NetworkConfig.SessionMode = m_SessionModeType; + m_ServerNetworkManager.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_SessionModeType == SessionModeTypes.DistributedAuthority; + + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.SessionMode = m_SessionModeType; + client.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_SessionModeType == SessionModeTypes.DistributedAuthority; + } +#endif m_Dude_NetObjs = new Transform[setCount]; m_Dude_LeftArm_NetObjs = new Transform[setCount]; m_Dude_RightArm_NetObjs = new Transform[setCount]; diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs.meta index bcd376301e..7e280235f5 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ee0fd7f2f6c544c59849f815c90128b5 +guid: cbe29dfc87668c24c9c29dd354e6751e MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity index 7640744d8d..35444ab962 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641275, b: 0.5748172, a: 1} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -104,7 +104,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +117,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -150,9 +150,17 @@ MeshCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 126426935} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 4 + serializedVersion: 5 m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} @@ -176,7 +184,7 @@ MeshRenderer: m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: - - {fileID: 2100000, guid: 03e094ab168544996a7d1d6dcdc60058, type: 2} + - {fileID: 2100000, guid: 0e597626cbe10b140ab0de9400e6bcaa, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -213,12 +221,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 126426935} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 10, y: 1, z: 10} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &136229882 GameObject: @@ -247,12 +256,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 136229882} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0.13052616, w: 0.9914449} m_LocalPosition: {x: 0.6, y: 0.2, z: 0} m_LocalScale: {x: 0.3, y: 0.8, z: 0.3} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1233633000} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 15} --- !u!136 &136229884 CapsuleCollider: @@ -262,8 +272,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 136229882} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5 m_Height: 2 m_Direction: 1 @@ -330,8 +349,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 1291518364 + GlobalObjectIdHash: 3378737312 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!1 &223562595 @@ -357,14 +383,15 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 223562595} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 877191011} - {fileID: 1646178338} m_Father: {fileID: 753527895} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &249733529 GameObject: @@ -392,12 +419,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 249733529} + serializedVersion: 2 m_LocalRotation: {x: -0.42261827, y: 0, z: 0, w: 0.9063079} m_LocalPosition: {x: 0, y: 1.25, z: 2.5} m_LocalScale: {x: 4, y: 2.3, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1904043517} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: -50, y: 0, z: 0} --- !u!65 &249733531 BoxCollider: @@ -407,9 +435,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 249733529} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &249733532 @@ -432,7 +468,7 @@ MeshRenderer: m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: - - {fileID: 2100000, guid: 52c34e1beacd44c9bb0f6375c891b4cf, type: 2} + - {fileID: 2100000, guid: cb2191cfdbf1c9548b6d2705cd1598b3, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -488,12 +524,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 305396494} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2027339602} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!135 &305396496 SphereCollider: @@ -503,9 +540,17 @@ SphereCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 305396494} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Radius: 0.5 m_Center: {x: 0, y: 0, z: 0} --- !u!23 &305396497 @@ -585,12 +630,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 453914372} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: -0.13052624, w: 0.9914449} m_LocalPosition: {x: -0.6, y: 0.2, z: 0} m_LocalScale: {x: 0.3, y: 0.8, z: 0.3} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1233633000} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: -15} --- !u!136 &453914374 CapsuleCollider: @@ -600,8 +646,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 453914372} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5 m_Height: 2 m_Direction: 1 @@ -668,8 +723,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 2501551336 + GlobalObjectIdHash: 3875094526 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!1 &534941258 @@ -698,12 +760,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 534941258} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068} m_LocalPosition: {x: 2.5, y: -1, z: 3.5} m_LocalScale: {x: 2, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1095337960} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90} --- !u!136 &534941260 CapsuleCollider: @@ -713,8 +776,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 534941258} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5000001 m_Height: 2 m_Direction: 1 @@ -795,12 +867,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 590215650} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068} m_LocalPosition: {x: 2.5, y: -1, z: -3.5} m_LocalScale: {x: 2, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1095337960} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90} --- !u!136 &590215652 CapsuleCollider: @@ -810,8 +883,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 590215650} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5000001 m_Height: 2 m_Direction: 1 @@ -952,12 +1034,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 598123439} + serializedVersion: 2 m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 3, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} --- !u!1 &753527891 GameObject: @@ -987,8 +1070,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 753527891} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5 m_Height: 2 m_Direction: 1 @@ -1050,15 +1142,16 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 753527891} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 3, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 779041968} - {fileID: 1233633000} - {fileID: 223562596} m_Father: {fileID: 1618484849} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &753527896 MonoBehaviour: @@ -1072,8 +1165,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 3039817976 + GlobalObjectIdHash: 404955689 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!1 &779041967 @@ -1102,12 +1202,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 779041967} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1.4, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 753527895} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!135 &779041969 SphereCollider: @@ -1117,9 +1218,17 @@ SphereCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 779041967} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Radius: 0.5 m_Center: {x: 0, y: 0, z: 0} --- !u!23 &779041970 @@ -1199,12 +1308,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 877191010} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0.3, y: -1.4, z: 0} m_LocalScale: {x: 0.30000004, y: 0.8, z: 0.3} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 223562596} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &877191012 MonoBehaviour: @@ -1218,8 +1328,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 4012496145 + GlobalObjectIdHash: 1943698069 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!136 &877191013 @@ -1230,8 +1347,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 877191010} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5 m_Height: 2 m_Direction: 1 @@ -1312,12 +1438,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1042922634} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068} m_LocalPosition: {x: -2.5, y: -1, z: -3.5} m_LocalScale: {x: 2, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1095337960} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90} --- !u!136 &1042922636 CapsuleCollider: @@ -1327,8 +1454,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1042922634} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5000001 m_Height: 2 m_Direction: 1 @@ -1406,16 +1542,17 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1095337959} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 2102441317} - {fileID: 534941259} - {fileID: 1042922635} - {fileID: 590215651} m_Father: {fileID: 1907713851} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1139821555 GameObject: @@ -1443,12 +1580,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1139821555} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 5, y: 2, z: 10} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1904043517} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!65 &1139821557 BoxCollider: @@ -1458,9 +1596,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1139821555} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &1139821558 @@ -1483,7 +1629,7 @@ MeshRenderer: m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: - - {fileID: 2100000, guid: 52c34e1beacd44c9bb0f6375c891b4cf, type: 2} + - {fileID: 2100000, guid: cb2191cfdbf1c9548b6d2705cd1598b3, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -1536,14 +1682,15 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1233632999} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 136229883} - {fileID: 453914373} m_Father: {fileID: 753527895} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1346726830 GameObject: @@ -1578,8 +1725,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 4270594269 + GlobalObjectIdHash: 472884780 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!65 &1346726832 @@ -1590,9 +1744,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1346726830} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &1346726833 @@ -1615,7 +1777,7 @@ MeshRenderer: m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: - - {fileID: 2100000, guid: d8e0487da5b3d4659909d18d138cff5a, type: 2} + - {fileID: 2100000, guid: 08e0c61b5a83bfb46a037cbecbebc671, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -1652,12 +1814,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1346726830} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 3, y: 0.5, z: 3} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1618484849} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1346726836 MonoBehaviour: @@ -1668,7 +1831,7 @@ MonoBehaviour: m_GameObject: {fileID: 1346726830} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9b9fb5b91091c4036bb4b0321b79f6c8, type: 3} + m_Script: {fileID: 11500000, guid: 0ca38a7c14d657e4fa7a2d73b4991ff6, type: 3} m_Name: m_EditorClassIdentifier: --- !u!1 &1463134322 @@ -1711,9 +1874,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -1747,12 +1918,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1463134322} + serializedVersion: 2 m_LocalRotation: {x: 0.2588191, y: 0, z: 0, w: 0.9659258} m_LocalPosition: {x: 0, y: 10, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 30, y: 0, z: 0} --- !u!1 &1522146278 GameObject: @@ -1780,12 +1952,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1522146278} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1.75, z: 1} m_LocalScale: {x: 4.5, y: 1.5, z: 2} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1904043517} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!65 &1522146280 BoxCollider: @@ -1795,9 +1968,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1522146278} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &1522146281 @@ -1820,7 +2001,7 @@ MeshRenderer: m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: - - {fileID: 2100000, guid: 52c34e1beacd44c9bb0f6375c891b4cf, type: 2} + - {fileID: 2100000, guid: cb2191cfdbf1c9548b6d2705cd1598b3, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -1873,15 +2054,16 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1618484848} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 753527895} - {fileID: 1346726835} - {fileID: 1907713851} m_Father: {fileID: 0} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1646178337 GameObject: @@ -1910,12 +2092,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1646178337} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: -0.3, y: -1.4, z: 0} m_LocalScale: {x: 0.30000004, y: 0.8, z: 0.3} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 223562596} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1646178339 MonoBehaviour: @@ -1929,8 +2112,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 1239503228 + GlobalObjectIdHash: 1041897422 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!136 &1646178340 @@ -1941,8 +2131,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1646178337} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5 m_Height: 2 m_Direction: 1 @@ -2020,16 +2219,17 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1904043516} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1139821556} - {fileID: 1522146279} - {fileID: 249733530} - {fileID: 2027339602} m_Father: {fileID: 1907713851} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1907713849 GameObject: @@ -2060,8 +2260,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 2387167595 + GlobalObjectIdHash: 1504619025 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!4 &1907713851 @@ -2071,14 +2278,15 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1907713849} + serializedVersion: 2 m_LocalRotation: {x: -0, y: 0.5, z: -0, w: 0.8660254} m_LocalPosition: {x: 0, y: 2, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1904043517} - {fileID: 1095337960} m_Father: {fileID: 1618484849} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 60, z: 0} --- !u!1 &2027339601 GameObject: @@ -2104,13 +2312,14 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2027339601} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1, z: -2} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 305396495} m_Father: {fileID: 1904043517} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2027339603 MonoBehaviour: @@ -2124,8 +2333,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 805918072 + GlobalObjectIdHash: 333240446 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!1 &2102441316 @@ -2154,12 +2370,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2102441316} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068} m_LocalPosition: {x: -2.5, y: -1, z: 3.5} m_LocalScale: {x: 2, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1095337960} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90} --- !u!136 &2102441318 CapsuleCollider: @@ -2169,8 +2386,17 @@ CapsuleCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2102441316} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 + serializedVersion: 2 m_Radius: 0.5000001 m_Height: 2 m_Direction: 1 @@ -2225,3 +2451,11 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2102441316} m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1463134325} + - {fileID: 598123441} + - {fileID: 126426939} + - {fileID: 1618484849} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity.meta index 73144b7868..f850e33601 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7522f8723c5674939a3c24acfd83a688 +guid: abe92ece17d830e41a66dff7edc9245d DefaultImporter: externalObjects: {} userData: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs index 153dc285c9..3aa21cc412 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs @@ -1,5 +1,8 @@ using System.Collections; using System.Collections.Generic; +#if NGO_DAMODE +using NUnit.Framework; +#endif using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -26,6 +29,10 @@ public override void OnNetworkSpawn() } } +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class ParentDynamicUnderInScenePlaced : NetcodeIntegrationTest { private const string k_SceneToLoad = "GenericInScenePlacedObject"; @@ -33,6 +40,16 @@ public class ParentDynamicUnderInScenePlaced : NetcodeIntegrationTest private GameObject m_DynamicallySpawned; private bool m_SceneIsLoaded; +#if NGO_DAMODE + public ParentDynamicUnderInScenePlaced(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + + protected override IEnumerator OnSetup() + { + ParentDynamicUnderInScenePlacedHelper.Instances.Clear(); + return base.OnSetup(); + } + protected override void OnServerAndClientsCreated() { m_DynamicallySpawned = CreateNetworkObjectPrefab("DynamicObj"); @@ -94,6 +111,9 @@ private bool TestParentedAndNotInScenePlaced() return true; } +#if NGO_DAMODE + private ulong m_TargetScenePlacedId; +#endif /// /// Integration test that validates parenting dynamically spawned NetworkObjects /// under an in-scene placed NetworkObject works properly. @@ -116,6 +136,9 @@ public IEnumerator ParentUnderInSceneplaced() // Wait for the host-server's player to be parented under the in-scene placed NetworkObject yield return WaitForConditionOrTimeOut(TestParentedAndNotInScenePlaced); AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.IsSceneObject.Value}) | Was Parented ({m_FailedValidation.transform.position != null})"); +#if NGO_DAMODE + m_TargetScenePlacedId = m_ServerNetworkManager.LocalClient.PlayerObject.transform.parent.GetComponent().NetworkObjectId; +#endif // Now dynamically spawn a NetworkObject to also test dynamically spawned NetworkObjects being parented // under in-scene placed NetworkObjects @@ -125,11 +148,47 @@ public IEnumerator ParentUnderInSceneplaced() for (int i = 0; i < 5; i++) { yield return CreateAndStartNewClient(); +#if NGO_DAMODE + if (m_DistributedAuthority) + { + yield return WaitForConditionOrTimeOut(() => ParentPlayerToInSceneNetworkObjectFound(i)); + AssertOnTimeout($"[Client-{i + 1}] Failed to find in-scene placed NetworkObject or failed to parent under it!"); + } +#endif yield return WaitForConditionOrTimeOut(TestParentedAndNotInScenePlaced); AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.IsSceneObject.Value}) | Was Parented ({m_FailedValidation.transform.position != null})"); } } +#if NGO_DAMODE + /// + /// For distributed authority mode, we have to find the in-scene placed object to parent the newly spawned player under + /// + private bool ParentPlayerToInSceneNetworkObjectFound(int index) + { + if (m_ClientNetworkManagers.Length - 1 != index) + { + return false; + } + var clientId = m_ClientNetworkManagers[index].LocalClientId; + if (!s_GlobalNetworkObjects.ContainsKey(clientId)) + { + return false; + } + + if (!s_GlobalNetworkObjects[clientId].ContainsKey(m_TargetScenePlacedId)) + { + return false; + } + + if (!m_ClientNetworkManagers[index].LocalClient.PlayerObject.TrySetParent(s_GlobalNetworkObjects[clientId][m_TargetScenePlacedId], false)) + { + return false; + } + + return true; + } +#endif private void SceneManager_OnLoadComplete(ulong clientId, string sceneName, LoadSceneMode loadSceneMode) { if (clientId == m_ServerNetworkManager.LocalClientId && sceneName == k_SceneToLoad) diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs.meta index 475de27504..acf51ab560 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4afe0bcb2a387094bb99b8cb6da1d0e0 +guid: 3e1743594c30a284f9dd355e6754bb1a MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity index 4b846072ec..89076e29f4 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity @@ -104,7 +104,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +117,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -152,8 +152,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 2988556371 + GlobalObjectIdHash: 879667119 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!4 &69934939 @@ -163,6 +170,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 69934937} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} m_LocalPosition: {x: 10, y: 10, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} @@ -170,7 +178,6 @@ Transform: m_Children: - {fileID: 1353280373} m_Father: {fileID: 0} - m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} --- !u!1 &118026864 GameObject: @@ -230,13 +237,13 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} m_Name: m_EditorClassIdentifier: - RunInBackground: 1 - LogLevel: 1 + DistributedAuthorityReset: 0 NetworkConfig: ProtocolVersion: 0 NetworkTransport: {fileID: 118026865} PlayerPrefab: {fileID: 0} - NetworkPrefabs: [] + Prefabs: + NetworkPrefabsLists: [] TickRate: 30 ClientConnectionBufferTimeout: 10 ConnectionApproval: 0 @@ -252,6 +259,13 @@ MonoBehaviour: LoadSceneTimeOut: 120 SpawnTimeout: 1 EnableNetworkLogs: 1 + SessionMode: 1 + UseCMBService: 0 + ClientSidePlayerSpawning: 0 + InterestManagementCellSize: 512 + OldPrefabList: [] + RunInBackground: 1 + LogLevel: 1 --- !u!4 &118026867 Transform: m_ObjectHideFlags: 0 @@ -259,13 +273,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 118026864} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0.5, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &118026868 MonoBehaviour: @@ -302,6 +316,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 153482222} + serializedVersion: 2 m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} m_LocalPosition: {x: 2, y: 0, z: 2} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} @@ -309,7 +324,6 @@ Transform: m_Children: - {fileID: 1934730583} m_Father: {fileID: 1351092234} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} --- !u!1 &295758851 GameObject: @@ -387,7 +401,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -406,7 +422,6 @@ RectTransform: - {fileID: 844324455} - {fileID: 605493857} m_Father: {fileID: 1392712516} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -437,13 +452,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 321630914} + serializedVersion: 2 m_LocalRotation: {x: -0.011012609, y: -0, z: -0, w: 0.99993944} m_LocalPosition: {x: 0, y: 5, z: -15} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1392712516} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: -1.262, y: 0, z: 0} --- !u!20 &321630917 Camera: @@ -459,9 +474,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -511,6 +534,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 364512791} + serializedVersion: 2 m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} m_LocalPosition: {x: 2, y: 0, z: 2} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} @@ -518,7 +542,6 @@ Transform: m_Children: - {fileID: 700085893} m_Father: {fileID: 366400900} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} --- !u!1 &366400899 GameObject: @@ -543,6 +566,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 366400899} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} m_LocalPosition: {x: 10, y: 10, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} @@ -550,7 +574,6 @@ Transform: m_Children: - {fileID: 364512792} m_Father: {fileID: 0} - m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} --- !u!1 &505911173 GameObject: @@ -629,6 +652,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 505911173} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -636,7 +660,6 @@ Transform: m_Children: - {fileID: 1601792191} m_Father: {fileID: 733348968} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &505911177 MonoBehaviour: @@ -650,8 +673,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 1818541129 + GlobalObjectIdHash: 953362924 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!114 &505911178 @@ -700,13 +730,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 518555745} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1353280373} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &518555747 MonoBehaviour: @@ -720,8 +750,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 4284572620 + GlobalObjectIdHash: 3696641308 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!1 &582431330 @@ -748,6 +785,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 582431330} + serializedVersion: 2 m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} m_LocalPosition: {x: 2, y: 0, z: 2} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} @@ -755,7 +793,6 @@ Transform: m_Children: - {fileID: 1546803112} m_Father: {fileID: 1282066021} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} --- !u!114 &582431332 MonoBehaviour: @@ -769,8 +806,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 3027368638 + GlobalObjectIdHash: 2064500905 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 0 --- !u!1 &605493856 @@ -798,13 +842,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 605493856} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 295758855} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &605493858 MonoBehaviour: @@ -918,13 +962,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 631269575} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1601792191} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &631269579 MonoBehaviour: @@ -938,8 +982,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 1688266703 + GlobalObjectIdHash: 670951037 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!114 &631269580 @@ -973,8 +1024,8 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 694186464} - - component: {fileID: 694186463} - component: {fileID: 694186462} + - component: {fileID: 694186465} m_Layer: 0 m_Name: AutoSyncManager m_TagString: Untagged @@ -994,11 +1045,33 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 2343724113 + GlobalObjectIdHash: 1826507586 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 ---- !u!114 &694186463 +--- !u!4 &694186464 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 694186461} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 561, y: 144.52359, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &694186465 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -1007,7 +1080,7 @@ MonoBehaviour: m_GameObject: {fileID: 694186461} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3326399105afcf4459b51991aeeaacd2, type: 3} + m_Script: {fileID: 11500000, guid: 6cb9810cec78d7e4ca96c44f73bbbcf1, type: 3} m_Name: m_EditorClassIdentifier: WithNetworkObjectAutoSyncOn: {fileID: 69934937} @@ -1018,21 +1091,6 @@ MonoBehaviour: NetworkObjectAutoSyncOffTransforms: [] GameObjectAutoSyncOnTransforms: [] GameObjectAutoSyncOffTransforms: [] ---- !u!4 &694186464 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 694186461} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 561, y: 144.52359, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 8 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &700085892 GameObject: m_ObjectHideFlags: 0 @@ -1057,13 +1115,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 700085892} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 364512792} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &700085894 MonoBehaviour: @@ -1077,8 +1135,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 1482043520 + GlobalObjectIdHash: 3183342136 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 0 --- !u!1 &733348965 @@ -1158,6 +1223,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 733348965} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -1165,7 +1231,6 @@ Transform: m_Children: - {fileID: 505911176} m_Father: {fileID: 945457593} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &733348969 MonoBehaviour: @@ -1179,8 +1244,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 1181626381 + GlobalObjectIdHash: 892840195 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!114 &733348970 @@ -1210,6 +1282,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 295758855} m_Modifications: - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, @@ -1323,6 +1396,9 @@ PrefabInstance: value: ConnectionModeButtons objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: d725b5588e1b956458798319e6541d84, type: 3} --- !u!224 &844324455 stripped RectTransform: @@ -1357,6 +1433,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 945457592} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -1364,7 +1441,6 @@ Transform: m_Children: - {fileID: 733348968} m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &945457594 MeshRenderer: @@ -1428,8 +1504,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 3960212516 + GlobalObjectIdHash: 2706935065 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!114 &945457597 @@ -1483,8 +1566,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 3624362024 + GlobalObjectIdHash: 2900709904 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 0 --- !u!4 &1282066021 @@ -1494,6 +1584,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1282066019} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} m_LocalPosition: {x: 10, y: 10, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} @@ -1501,7 +1592,6 @@ Transform: m_Children: - {fileID: 582431331} m_Father: {fileID: 0} - m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} --- !u!1 &1343771061 GameObject: @@ -1527,13 +1617,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1343771061} + serializedVersion: 2 m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 2.5, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1392712516} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} --- !u!108 &1343771063 Light: @@ -1620,6 +1710,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1351092233} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.53729963, z: 0, w: 0.8433915} m_LocalPosition: {x: 10, y: 10, z: 10} m_LocalScale: {x: 1, y: 1, z: 1} @@ -1627,7 +1718,6 @@ Transform: m_Children: - {fileID: 153482223} m_Father: {fileID: 0} - m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 65, z: 0} --- !u!1 &1353280372 GameObject: @@ -1653,6 +1743,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1353280372} + serializedVersion: 2 m_LocalRotation: {x: 0.18730325, y: -0.036408048, z: 0.18730325, w: 0.963592} m_LocalPosition: {x: 2, y: 0, z: 2} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} @@ -1660,7 +1751,6 @@ Transform: m_Children: - {fileID: 518555746} m_Father: {fileID: 69934939} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 22, y: 0, z: 22} --- !u!114 &1353280374 MonoBehaviour: @@ -1674,8 +1764,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 2685148660 + GlobalObjectIdHash: 1172264353 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!1 &1392712513 @@ -1703,6 +1800,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1392712513} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -1712,7 +1810,6 @@ Transform: - {fileID: 321630915} - {fileID: 1343771062} m_Father: {fileID: 0} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1392712517 MonoBehaviour: @@ -1738,8 +1835,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 2701768183 + GlobalObjectIdHash: 1990214503 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!1 &1546803111 @@ -1766,13 +1870,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1546803111} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 582431331} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1546803113 MonoBehaviour: @@ -1786,8 +1890,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 136657534 + GlobalObjectIdHash: 4223257018 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 0 --- !u!1 &1601792188 @@ -1867,6 +1978,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1601792188} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -1874,7 +1986,6 @@ Transform: m_Children: - {fileID: 631269578} m_Father: {fileID: 505911176} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1601792192 MonoBehaviour: @@ -1888,8 +1999,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 65702661 + GlobalObjectIdHash: 616258735 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!114 &1601792193 @@ -1937,6 +2055,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1839922012} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0.5, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -1944,7 +2063,6 @@ Transform: m_Children: - {fileID: 2062433907} m_Father: {fileID: 0} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1934730582 GameObject: @@ -1970,13 +2088,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1934730582} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 153482223} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1934730584 MonoBehaviour: @@ -1990,8 +2108,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 638354969 + GlobalObjectIdHash: 2656819585 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 0 --- !u!1 &2062433906 @@ -2019,13 +2144,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2062433906} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1839922013} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2062433908 MonoBehaviour: @@ -2039,8 +2164,15 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 136663336 + GlobalObjectIdHash: 1341406513 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!114 &2062433909 @@ -2055,3 +2187,16 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8384b01f654da304ebe165da1624cf56, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 945457593} + - {fileID: 118026867} + - {fileID: 1839922013} + - {fileID: 1392712516} + - {fileID: 69934939} + - {fileID: 1282066021} + - {fileID: 1351092234} + - {fileID: 366400900} + - {fileID: 694186464} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta index b8b7ca5ac5..87847a92f4 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 49fd14bff1eceda4f9299721a9029750 +guid: d04d395e163705441935990f702c782f DefaultImporter: externalObjects: {} userData: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index c275ad45d4..b9f4f6f47c 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -4,11 +4,16 @@ using TestProject.ManualTests; using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation { private const string k_BaseSceneToLoad = "UnitTestBaseScene"; @@ -21,6 +26,9 @@ public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation protected override int NumberOfClients => 2; +#if NGO_DAMODE + public ParentingInSceneObjectsTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif protected override void OnOneTimeSetup() { NetworkManagerTestDisabler.IsIntegrationTest = true; @@ -56,6 +64,8 @@ private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode mode) protected override IEnumerator OnTearDown() { + ParentingAutoSyncManager.Reset(); + if (m_BaseSceneLoaded.IsValid() && m_BaseSceneLoaded.isLoaded) { SceneManager.UnloadSceneAsync(m_BaseSceneLoaded); @@ -66,17 +76,20 @@ protected override IEnumerator OnTearDown() private void GeneratePositionDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler) { - m_ErrorValidationLog.AppendLine($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s position {clientHandler.transform.position} does not equal the server-side position {serverHandler.transform.position}"); + m_ErrorValidationLog.AppendLine($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s " + + $"position {GetVector3Values(clientHandler.transform.position)} does not equal the server-side position {GetVector3Values(serverHandler.transform.position)}"); } private void GenerateRotationDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler) { - m_ErrorValidationLog.Append($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s rotation {clientHandler.transform.eulerAngles} does not equal the server-side rotation {serverHandler.transform.eulerAngles}"); + m_ErrorValidationLog.AppendLine($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s " + + $"rotation {GetVector3Values(clientHandler.transform.eulerAngles)} does not equal the server-side rotation {GetVector3Values(serverHandler.transform.eulerAngles)}"); } private void GenerateScaleDoesNotMatch(InSceneParentChildHandler serverHandler, InSceneParentChildHandler clientHandler) { - m_ErrorValidationLog.AppendLine($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s scale {clientHandler.transform.localScale} does not equal the server-side scale {serverHandler.transform.localScale}"); + m_ErrorValidationLog.AppendLine($"[Client-{clientHandler.NetworkManager.LocalClientId}] {nameof(NetworkObject)}-{clientHandler.NetworkObjectId}'s " + + $"scale {GetVector3Values(clientHandler.transform.localScale)} does not equal the server-side scale {GetVector3Values(serverHandler.transform.localScale)}"); } private void GenerateParentIsNotCorrect(InSceneParentChildHandler handler, bool shouldHaveParent, bool isStillSpawnedCheck = false) @@ -114,13 +127,13 @@ private void GenerateTransformInfo(InSceneParentChildHandler serverHandler, InSc } } - private bool ValidateClientAgainstServerTransformValues() + private bool ValidateClientsAgainstAuthorityTransformValues() { // We reset this each time because we are only interested in the last time it checked and failed m_ErrorValidationLog.Clear(); var passed = true; var failingClient = (ulong)0; - foreach (var instance in InSceneParentChildHandler.ServerRelativeInstances) + foreach (var instance in InSceneParentChildHandler.AuthorityRelativeInstances) { var serverInstanceTransform = instance.Value.transform; foreach (var clientInstances in InSceneParentChildHandler.ClientRelativeInstances) @@ -163,7 +176,7 @@ private bool ValidateClientAgainstServerTransformValues() private bool ValidateAllChildrenParentingStatus(bool checkForParent) { m_ErrorValidationLog.Clear(); - foreach (var instance in InSceneParentChildHandler.ServerRelativeInstances) + foreach (var instance in InSceneParentChildHandler.AuthorityRelativeInstances) { if (!instance.Value.IsRootParent) { @@ -238,22 +251,40 @@ public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace) yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); + var debugWait = new WaitForSeconds(2); // [Currently Connected Clients] // remove the parents, change all transform values, and re-parent - InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); - AssertOnTimeout($"Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + InSceneParentChildHandler.AuthorityRootParent.DeparentSetValuesAndReparent(); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); + + + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"Timed out waiting for all clients transform values to match the authority transform values!\n {m_ErrorValidationLog}"); // [Late Join Client #1] // Make sure the late joining client synchronizes properly yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); - AssertOnTimeout($"[Late Join 1] Timed out waiting for the late joining client's transform values to match the server transform values!\n {m_ErrorValidationLog}"); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Late Join 1] Timed out waiting for the late joining client's transform values to match the authority transform values!\n {m_ErrorValidationLog}"); // Remove the parents from all of the children - InSceneParentChildHandler.ServerRootParent.DeparentAllChildren(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); - AssertOnTimeout($"[Late Join 1] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + InSceneParentChildHandler.AuthorityRootParent.DeparentAllChildren(); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Late Join 1] Timed out waiting for all clients transform values to match the authority transform values!\n {m_ErrorValidationLog}"); yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(false)); AssertOnTimeout($"[Late Join 1] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); @@ -261,48 +292,78 @@ public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace) // [Late Join Client #2] // Make sure the late joining client synchronizes properly with all children having their parent removed yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); - AssertOnTimeout($"[Late Join 2] Timed out waiting for the late joining client's transform values to match the server transform values!\n {m_ErrorValidationLog}"); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Late Join 2] Timed out waiting for the late joining client's transform values to match the authority transform values!\n {m_ErrorValidationLog}"); // Just a sanity check that late joining client #2 has no child parented yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(false)); - AssertOnTimeout($"[Late Join 2] Timed out waiting for late joined client's children objects to have no parent!\n {m_ErrorValidationLog}"); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Late Join 2] Timed out verifying late joined client's children objects to have no parent!\n {m_ErrorValidationLog}"); // Finally, re-parent all of the children to make sure late joining client #2 synchronizes properly - InSceneParentChildHandler.ServerRootParent.ReParentAllChildren(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); - AssertOnTimeout($"[Late Join 2] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + InSceneParentChildHandler.AuthorityRootParent.ReParentAllChildren(); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Late Join 2] Timed out waiting for all clients' transform values to match the authority transform values!\n {m_ErrorValidationLog}"); yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); - AssertOnTimeout($"[Late Join 2] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Late Join 2] Timed out waiting for all clients' to re-parent all targeted child objects!\n {m_ErrorValidationLog}"); // Now run through many iterations where we remove the parents, set the parents, and while // the parents are being set the InSceneParentChildHandler assigns new position, rotation, and scale values // in the OnNetworkObjectParentChanged overridden method on the server side only for (int i = 0; i < k_NumIterationsDeparentReparent; i++) { - InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); + InSceneParentChildHandler.AuthorityRootParent.DeparentSetValuesAndReparent(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); - AssertOnTimeout($"[Final Pass] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Final Pass][Deparent-Reparent-{i}] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); - AssertOnTimeout($"[Final Pass] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); + if (s_GlobalTimeoutHelper.TimedOut) + { + InSceneParentChildHandler.AuthorityRootParent.CheckChildren(); + yield return debugWait; + } + AssertOnTimeout($"[Final Pass][Deparent-Reparent-{i}] Timed out waiting for all children to be removed from their parent!\n {m_ErrorValidationLog}"); } // In the final pass, we remove the second generation nested child - var firstGenChild = InSceneParentChildHandler.ServerRootParent.transform.GetChild(0); + var firstGenChild = InSceneParentChildHandler.AuthorityRootParent.transform.GetChild(0); var secondGenChild = firstGenChild.GetChild(0); var secondGenChildNetworkObject = secondGenChild.GetComponent(); Assert.True(secondGenChildNetworkObject.TrySetParent((NetworkObject)null, false), $"[Final Pass] Failed to remove the parent from the second generation child!"); // Validate all transform values match - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); AssertOnTimeout($"[Final Pass] Timed out waiting for all clients transform values to match the server transform values after the second generation child's parent was removed!\n {m_ErrorValidationLog}"); // Now run through one last de-parent, re-parent, and set new values pass to make sure everything still synchronizes - InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); + InSceneParentChildHandler.AuthorityRootParent.DeparentSetValuesAndReparent(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); AssertOnTimeout($"[Final Pass - Last Test] Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); yield return WaitForConditionOrTimeOut(() => ValidateAllChildrenParentingStatus(true)); @@ -316,11 +377,11 @@ private bool ValidateRootParentDespawnedAndChildAtRoot() { m_ErrorValidationLog.Clear(); - var childOfRoot_ServerSide = InSceneParentChildHandler.ServerRootParent.GetChild(); - if (InSceneParentChildHandler.ServerRootParent.IsSpawned) + var childOfRoot_ServerSide = InSceneParentChildHandler.AuthorityRootParent.GetChild(); + if (InSceneParentChildHandler.AuthorityRootParent.IsSpawned) { m_ErrorValidationLog.Append("Server-Side root parent is still spawned!"); - GenerateParentIsNotCorrect(childOfRoot_ServerSide, false, InSceneParentChildHandler.ServerRootParent.IsSpawned); + GenerateParentIsNotCorrect(childOfRoot_ServerSide, false, InSceneParentChildHandler.AuthorityRootParent.IsSpawned); return false; } @@ -370,12 +431,12 @@ public IEnumerator DespawnParentTest([Values] ParentingSpace parentingSpace) // [Currently Connected Clients] // remove the parents, change all transform values, and re-parent - InSceneParentChildHandler.ServerRootParent.DeparentSetValuesAndReparent(); - yield return WaitForConditionOrTimeOut(ValidateClientAgainstServerTransformValues); + InSceneParentChildHandler.AuthorityRootParent.DeparentSetValuesAndReparent(); + yield return WaitForConditionOrTimeOut(ValidateClientsAgainstAuthorityTransformValues); AssertOnTimeout($"Timed out waiting for all clients transform values to match the server transform values!\n {m_ErrorValidationLog}"); // Now despawn the root parent - InSceneParentChildHandler.ServerRootParent.NetworkObject.Despawn(false); + InSceneParentChildHandler.AuthorityRootParent.NetworkObject.Despawn(false); // Verify all clients despawned the parent object and the child of the parent has root as its parent yield return WaitForConditionOrTimeOut(ValidateRootParentDespawnedAndChildAtRoot); @@ -424,6 +485,12 @@ public IEnumerator InSceneNestedUnderGameObjectTest() /// private bool AllClientInstancesMatchServerInstance() { + m_ErrorValidationLog.Clear(); + if (ParentingAutoSyncManager.ServerInstance == 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]; @@ -433,16 +500,19 @@ private bool AllClientInstancesMatchServerInstance() 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; } } @@ -457,16 +527,19 @@ private bool AllClientInstancesMatchServerInstance() 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; } } @@ -481,16 +554,19 @@ private bool AllClientInstancesMatchServerInstance() 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; } } @@ -505,16 +581,19 @@ private bool AllClientInstancesMatchServerInstance() var clientTransformToTest = clientRelativeAutoSyncManager.GameObjectAutoSyncOffTransforms[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}"); 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}"); 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}"); return false; } } @@ -541,7 +620,7 @@ public IEnumerator InSceneNestedAutoSyncObjectTest() AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); yield return WaitForConditionOrTimeOut(AllClientInstancesMatchServerInstance); - AssertOnTimeout($"Timed out waiting for all client transforms to match the server-side values in test scene {k_TestSceneToLoad}!"); + AssertOnTimeout($"Timed out waiting for all client transforms to match the server-side values in test scene {k_TestSceneToLoad}!\n {m_ErrorValidationLog.ToString()}"); } } } diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta index 383199e0df..4487bdd582 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: eb1bdec28131915419a2825132406932 +guid: e5024637f645a694b83e79c89a71ee62 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs index 93ac3eac93..0d911dc5d7 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs @@ -11,6 +11,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class ParentingWorldPositionStaysTests : IntegrationTestWithApproximation { private const int k_NestedChildren = 10; @@ -156,6 +160,10 @@ public enum NetworkTransformSettings private Quaternion m_ChildStartRotation = Quaternion.Euler(-35.0f, 0.0f, -180.0f); private Vector3 m_ChildStartScale = Vector3.one; +#if NGO_DAMODE + public ParentingWorldPositionStaysTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + protected override IEnumerator OnSetup() { TestComponentHelper.ClientsRegistered.Clear(); diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta index 3b3e7d27fb..9035651c91 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ea191714be7765a41ba7cbd26b6ecfa0 +guid: f7a30f85ec3dafb4f93007ce13c0ec47 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/PlayerPrefab.prefab.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/PlayerPrefab.prefab.meta index c0781023b2..58d609faa9 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/PlayerPrefab.prefab.meta +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/PlayerPrefab.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 421bcf732fe69486d8abecfa5eee63bb +guid: c3f26b8822ab9ec4dbda31fd1bb9965d PrefabImporter: externalObjects: {} userData: diff --git a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs index 8b05791995..16bc79e900 100644 --- a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs +++ b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs @@ -12,7 +12,7 @@ namespace TestProject.RuntimeTests { - + // DAMODE-TODO: When scene management is working in distributed authority mode we need to update this test [TestFixture(SceneManagementTypes.SceneManagementEnabled)] [TestFixture(SceneManagementTypes.SceneManagementDisabled)] public class PrefabExtendedTests : NetcodeIntegrationTest @@ -212,13 +212,6 @@ public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods insta var firstError = $"[Netcode] Failed to create object locally. [globalObjectIdHash={gloabalObjectId}]. NetworkPrefab could not be found. Is the prefab registered with NetworkManager?"; var secondError = $"[Netcode] Failed to spawn NetworkObject for Hash {gloabalObjectId}."; m_InstantiateAndSpawnType = instantiateAndSpawnType; - if (!m_SceneManagementEnabled) - { - // When scene management is disabled, in-scene defined NetworkObjects are not synchronized. - // Expect 2 error messages about not being able to find the in-scene defined NetworkObject in the PrefabTestScene. - LogAssert.Expect(LogType.Error, firstError); - LogAssert.Expect(LogType.Error, secondError); - } // We have to spawn the first client manually in order to account for the errors when scene management is disabled. // Spawn the client prior to dynamically spawning our prefab instances. Two of the in-scene placed NetworkObjects @@ -278,20 +271,11 @@ public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods insta // Validate that the client synchronized with the in-scene placed and dynamically spawned NetworkObjects yield return WaitForConditionOrTimeOut(ValidateAllClientsSpawnedObjects); - AssertOnTimeout($"[First Stage] Validating spawned objects faild with the following error: {m_ErrorLog}"); + AssertOnTimeout($"[First Stage][{instantiateAndSpawnType}] Validating spawned objects faild with the following error: {m_ErrorLog}"); - // Finally, let's test a late joining client and make sure it synchronizes with the in-scene placed and - // dynamically spawned NetworkObjects - if (!m_SceneManagementEnabled) - { - // When scene management is disabled, in-scene defined NetworkObjects are not synchronized. - // Expect 2 error messages about not being able to find the in-scene defined NetworkObject in the PrefabTestScene. - LogAssert.Expect(LogType.Error, firstError); - LogAssert.Expect(LogType.Error, secondError); - } yield return CreateAndStartNewClient(); yield return WaitForConditionOrTimeOut(ValidateAllClientsSpawnedObjects); - AssertOnTimeout($"[Second Stage] Validating spawned objects faild with the following error: {m_ErrorLog}"); + AssertOnTimeout($"[Second Stage][{instantiateAndSpawnType}] Validating spawned objects faild with the following error: {m_ErrorLog}"); if (instantiateAndSpawnType != InstantiateAndSpawnMethods.Manual) { @@ -376,6 +360,4 @@ private NetworkObject InstantiateAndSpawn(NetworkObject prefabNetworkObject, Ins } } - - } diff --git a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs index 9460c758eb..90d728dfbf 100644 --- a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs +++ b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs @@ -8,6 +8,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest { public const string SceneToLoad = "InSceneNetworkObject"; @@ -15,6 +19,10 @@ public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest protected override int NumberOfClients => 0; protected Scene m_SceneLoaded; +#if NGO_DAMODE + public RespawnInSceneObjectsAfterShutdown(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + protected override void OnOneTimeSetup() { base.OnOneTimeSetup(); diff --git a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs index 780847b686..d5a7e17542 100644 --- a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs +++ b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs @@ -14,6 +14,9 @@ namespace TestProject.RuntimeTests /// Integration test to validate ClientRpcs will only /// send to observers of the NetworkObject /// +#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] +#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class RpcObserverTests : NetcodeIntegrationTest diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs index 832870d564..464d3a44b6 100644 --- a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs +++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs @@ -47,6 +47,11 @@ public void NetworkSerialize(BufferSerializer serializer.SerializeValue(ref Value); } } + +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class RpcUserSerializableTypesTest : NetcodeIntegrationTest { private UserSerializableClass m_UserSerializableClass; @@ -78,6 +83,10 @@ public class RpcUserSerializableTypesTest : NetcodeIntegrationTest protected override int NumberOfClients => 1; +#if NGO_DAMODE + public RpcUserSerializableTypesTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() { return NetworkManagerInstatiationMode.DoNotCreate; @@ -103,15 +112,32 @@ public IEnumerator NetworkSerializableTest() yield return StartServerAndClients(); // [Client-Side] We only need to get the client side Player's NetworkObject so we can grab that instance of the TestSerializationComponent - var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); - var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); - clientSideNetworkBehaviourClass.OnSerializableClassUpdated = OnClientReceivedUserSerializableClassUpdated; - clientSideNetworkBehaviourClass.OnSerializableStructUpdated = OnClientReceivedUserSerializableStructUpdated; - clientSideNetworkBehaviourClass.OnTemplateStructUpdated = OnClientReceivedUserSerializableTemplateStructUpdated; - clientSideNetworkBehaviourClass.OnNetworkSerializableTemplateStructUpdated = OnClientReceivedUserSerializableNetworkSerializableTemplateStructUpdated; clientSideNetworkBehaviourClass.OnTemplateStructsUpdated = OnClientReceivedUserSerializableTemplateStructsUpdated; - clientSideNetworkBehaviourClass.OnNetworkSerializableTemplateStructsUpdated = OnClientReceivedUserSerializableNetworkSerializableTemplateStructsUpdated; + // Use the client's instance of the + var targetContext = m_ClientNetworkManagers[0]; +#if NGO_DAMODE + // When in distributed authority mode: + // Owner Instances + // - Can send ClientRpcs + // - Receive ServerRpcs and ClientRpcs + // Non-Owner Instances + // - Can send ServerRpcs + // - Can receive ClientRpcs + if (m_DistributedAuthority) + { + // Use the server instance of the client's player + targetContext = m_ServerNetworkManager; + } +#endif + var clientContextPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), targetContext, clientContextPlayerResult); + + var clientContextNetworkBehaviourClass = clientContextPlayerResult.Result.gameObject.GetComponent(); + clientContextNetworkBehaviourClass.OnSerializableClassUpdated = OnClientReceivedUserSerializableClassUpdated; + clientContextNetworkBehaviourClass.OnSerializableStructUpdated = OnClientReceivedUserSerializableStructUpdated; + clientContextNetworkBehaviourClass.OnTemplateStructUpdated = OnClientReceivedUserSerializableTemplateStructUpdated; + clientContextNetworkBehaviourClass.OnNetworkSerializableTemplateStructUpdated = OnClientReceivedUserSerializableNetworkSerializableTemplateStructUpdated; clientContextNetworkBehaviourClass.OnTemplateStructsUpdated = OnClientReceivedUserSerializableTemplateStructsUpdated; + clientContextNetworkBehaviourClass.OnNetworkSerializableTemplateStructsUpdated = OnClientReceivedUserSerializableNetworkSerializableTemplateStructsUpdated; var userSerializableClass = new UserSerializableClass(); for (int i = 0; i < 32; i++) @@ -140,12 +166,12 @@ public IEnumerator NetworkSerializableTest() var networkSerializableT2vals = new[] { networkSerializableT2val }; var enumVals = new[] { enumVal }; - clientSideNetworkBehaviourClass.ClientStartTest(userSerializableClass); - clientSideNetworkBehaviourClass.ClientStartTest(userSerializableStruct); - clientSideNetworkBehaviourClass.ClientStartTest(t1val, t2val, enumVal); - clientSideNetworkBehaviourClass.ClientStartTest(networkSerializableT1val, networkSerializableT2val); - clientSideNetworkBehaviourClass.ClientStartTest(t1vals, t2vals, enumVals); - clientSideNetworkBehaviourClass.ClientStartTest(networkSerializableT1vals, networkSerializableT2vals); + clientContextNetworkBehaviourClass.ClientStartTest(userSerializableClass); + clientContextNetworkBehaviourClass.ClientStartTest(userSerializableStruct); + clientContextNetworkBehaviourClass.ClientStartTest(t1val, t2val, enumVal); + clientContextNetworkBehaviourClass.ClientStartTest(networkSerializableT1val, networkSerializableT2val); + clientContextNetworkBehaviourClass.ClientStartTest(t1vals, t2vals, enumVals); + clientContextNetworkBehaviourClass.ClientStartTest(networkSerializableT1vals, networkSerializableT2vals); // Wait until the test has finished or we time out var timeOutPeriod = Time.realtimeSinceStartup + 5; @@ -313,11 +339,31 @@ public IEnumerator ExtensionMethodRpcTest() serverIntListCalled && serverStrListCalled; }; - clientSideNetworkBehaviourClass.SendMyObjectServerRpc(obj); - clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(obj2); - clientSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefServerRpc(obj3); - clientSideNetworkBehaviourClass.SendIntListServerRpc(intList); - clientSideNetworkBehaviourClass.SendStringListServerRpc(strList); +#if NGO_DAMODE + // When in distributed authority mode: + // Owner Instances + // - Can send ClientRpcs + // - Receive ServerRpcs and ClientRpcs + // Non-Owner Instances + // - Can send ServerRpcs + // - Can receive ClientRpcs + if (m_DistributedAuthority) + { + serverSideNetworkBehaviourClass.SendMyObjectOwnerRpc(obj); + serverSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdOwnerRpc(obj2); + serverSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefOwnerRpc(obj3); + serverSideNetworkBehaviourClass.SendIntListOwnerRpc(intList); + serverSideNetworkBehaviourClass.SendStringListOwnerRpc(strList); + } + else +#endif + { + clientSideNetworkBehaviourClass.SendMyObjectServerRpc(obj); + clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(obj2); + clientSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefServerRpc(obj3); + clientSideNetworkBehaviourClass.SendIntListServerRpc(intList); + clientSideNetworkBehaviourClass.SendStringListServerRpc(strList); + } // Wait until the test has finished or we time out var timeOutPeriod = Time.realtimeSinceStartup + 5; @@ -487,11 +533,31 @@ public IEnumerator ExtensionMethodArrayRpcTest() serverIntListCalled && serverStrListCalled; }; - clientSideNetworkBehaviourClass.SendMyObjectServerRpc(objs); - clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(objs2); - clientSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefServerRpc(objs3); - clientSideNetworkBehaviourClass.SendIntListServerRpc(intList); - clientSideNetworkBehaviourClass.SendStringListServerRpc(strList); +#if NGO_DAMODE + // When in distributed authority mode: + // Owner Instances + // - Can send ClientRpcs + // - Receive ServerRpcs and ClientRpcs + // Non-Owner Instances + // - Can send ServerRpcs + // - Can receive ClientRpcs + if (m_DistributedAuthority) + { + serverSideNetworkBehaviourClass.SendMyObjectOwnerRpc(objs); + serverSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdOwnerRpc(objs2); + serverSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefOwnerRpc(objs3); + serverSideNetworkBehaviourClass.SendIntListOwnerRpc(intList); + serverSideNetworkBehaviourClass.SendStringListOwnerRpc(strList); + } + else +#endif + { + clientSideNetworkBehaviourClass.SendMyObjectServerRpc(objs); + clientSideNetworkBehaviourClass.SendMySharedObjectReferencedByIdServerRpc(objs2); + clientSideNetworkBehaviourClass.SendMyObjectPassedWithThisRefServerRpc(objs3); + clientSideNetworkBehaviourClass.SendIntListServerRpc(intList); + clientSideNetworkBehaviourClass.SendStringListServerRpc(strList); + } // Wait until the test has finished or we time out var timeOutPeriod = Time.realtimeSinceStartup + 5; @@ -630,15 +696,36 @@ public IEnumerator NetworkSerializableArrayTestHandler(int arraySize, bool sendN var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); var serverSideNetworkBehaviourClass = serverClientPlayerResult.Result.gameObject.GetComponent(); - serverSideNetworkBehaviourClass.OnSerializableClassesUpdatedServerRpc = OnServerReceivedUserSerializableClassesUpdated; - serverSideNetworkBehaviourClass.OnSerializableStructsUpdatedServerRpc = OnServerReceivedUserSerializableStructsUpdated; + // [Client-Side] Get the client side Player's NetworkObject so we can grab that instance of the TestCustomTypesArrayComponent var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent(); - clientSideNetworkBehaviourClass.OnSerializableClassesUpdatedClientRpc = OnClientReceivedUserSerializableClassesUpdated; - clientSideNetworkBehaviourClass.OnSerializableStructsUpdatedClientRpc = OnClientReceivedUserSerializableStructsUpdated; + +#if NGO_DAMODE + // When in distributed authority mode: + // Owner Instances + // - Can send ClientRpcs + // - Receive ServerRpcs and ClientRpcs + // Non-Owner Instances + // - Can send ServerRpcs + // - Can receive ClientRpcs + if (m_DistributedAuthority) + { + clientSideNetworkBehaviourClass.OnSerializableClassesUpdatedServerRpc = OnServerReceivedUserSerializableClassesUpdated; + clientSideNetworkBehaviourClass.OnSerializableStructsUpdatedServerRpc = OnServerReceivedUserSerializableStructsUpdated; + serverSideNetworkBehaviourClass.OnSerializableClassesUpdatedClientRpc = OnClientReceivedUserSerializableClassesUpdated; + serverSideNetworkBehaviourClass.OnSerializableStructsUpdatedClientRpc = OnClientReceivedUserSerializableStructsUpdated; + } + else +#endif + { + serverSideNetworkBehaviourClass.OnSerializableClassesUpdatedServerRpc = OnServerReceivedUserSerializableClassesUpdated; + serverSideNetworkBehaviourClass.OnSerializableStructsUpdatedServerRpc = OnServerReceivedUserSerializableStructsUpdated; + clientSideNetworkBehaviourClass.OnSerializableClassesUpdatedClientRpc = OnClientReceivedUserSerializableClassesUpdated; + clientSideNetworkBehaviourClass.OnSerializableStructsUpdatedClientRpc = OnClientReceivedUserSerializableStructsUpdated; + } m_UserSerializableClassArray = new List(); m_UserSerializableStructArray = new List(); @@ -662,14 +749,47 @@ public IEnumerator NetworkSerializableArrayTestHandler(int arraySize, bool sendN }; m_UserSerializableStructArray.Add(userSerializableStruct); } - - clientSideNetworkBehaviourClass.ClientStartTest(m_UserSerializableClassArray.ToArray()); - clientSideNetworkBehaviourClass.ClientStartStructTest(m_UserSerializableStructArray.ToArray()); +#if NGO_DAMODE + // When in distributed authority mode: + // Owner Instances + // - Can send ClientRpcs + // - Receive ServerRpcs and ClientRpcs + // Non-Owner Instances + // - Can send ServerRpcs + // - Can receive ClientRpcs + if (m_DistributedAuthority) + { + serverSideNetworkBehaviourClass.ClientStartTest(m_UserSerializableClassArray.ToArray()); + serverSideNetworkBehaviourClass.ClientStartStructTest(m_UserSerializableStructArray.ToArray()); + } + else +#endif + { + clientSideNetworkBehaviourClass.ClientStartTest(m_UserSerializableClassArray.ToArray()); + clientSideNetworkBehaviourClass.ClientStartStructTest(m_UserSerializableStructArray.ToArray()); + } } else { - clientSideNetworkBehaviourClass.ClientStartTest(null); - clientSideNetworkBehaviourClass.ClientStartStructTest(null); +#if NGO_DAMODE + // When in distributed authority mode: + // Owner Instances + // - Can send ClientRpcs + // - Receive ServerRpcs and ClientRpcs + // Non-Owner Instances + // - Can send ServerRpcs + // - Can receive ClientRpcs + if (m_DistributedAuthority) + { + serverSideNetworkBehaviourClass.ClientStartTest(null); + serverSideNetworkBehaviourClass.ClientStartStructTest(null); + } + else +#endif + { + clientSideNetworkBehaviourClass.ClientStartTest(null); + clientSideNetworkBehaviourClass.ClientStartStructTest(null); + } } // Wait until the test has finished or we time out @@ -831,21 +951,26 @@ public class TestSerializationComponent : NetworkBehaviour public delegate void OnMyObjectPassedWithThisRefUpdatedDelgateHandler(MyObjectPassedWithThisRef obj); public OnMyObjectPassedWithThisRefUpdatedDelgateHandler OnMyObjectPassedWithThisRefUpdated; + #region ClientStartTest(UserSerializableClass userSerializableClass) /// /// Starts the unit test and passes the UserSerializableClass from the client to the server /// /// public void ClientStartTest(UserSerializableClass userSerializableClass) { - SendServerSerializedDataClassServerRpc(userSerializableClass); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendOwnerSerializedDataClassRpc(userSerializableClass); + } + else +#endif + { + SendServerSerializedDataClassServerRpc(userSerializableClass); + } } - /// - /// Server receives the UserSerializableClass, modifies it, and sends it back - /// - /// - [ServerRpc(RequireOwnership = false)] - private void SendServerSerializedDataClassServerRpc(UserSerializableClass userSerializableClass) + private void ProcessSerializedDataClass(UserSerializableClass userSerializableClass) { userSerializableClass.MyintValue++; userSerializableClass.MyulongValue++; @@ -859,6 +984,29 @@ private void SendServerSerializedDataClassServerRpc(UserSerializableClass userSe { userSerializableClass.MyByteListValues.Add((byte)i); } + } + + [Rpc(SendTo.Owner)] + private void SendOwnerSerializedDataClassRpc(UserSerializableClass userSerializableClass) + { + ProcessSerializedDataClass(userSerializableClass); + SendNotOwnerSerializedDataClassRpc(userSerializableClass); + } + + [Rpc(SendTo.NotOwner)] + private void SendNotOwnerSerializedDataClassRpc(UserSerializableClass userSerializableClass) + { + OnSerializableClassUpdated?.Invoke(userSerializableClass); + } + + /// + /// Server receives the UserSerializableClass, modifies it, and sends it back + /// + /// + [ServerRpc(RequireOwnership = false)] + private void SendServerSerializedDataClassServerRpc(UserSerializableClass userSerializableClass) + { + ProcessSerializedDataClass(userSerializableClass); SendClientSerializedDataClassClientRpc(userSerializableClass); } @@ -871,10 +1019,39 @@ private void SendClientSerializedDataClassClientRpc(UserSerializableClass userSe { OnSerializableClassUpdated?.Invoke(userSerializableClass); } + #endregion + #region ClientStartTest(TemplatedType t1val, TemplatedType.NestedTemplatedType t2val, TemplatedType.Enum enumVal) public void ClientStartTest(TemplatedType t1val, TemplatedType.NestedTemplatedType t2val, TemplatedType.Enum enumVal) { - SendTemplateStructServerRpc(t1val, t2val, enumVal); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendTemplateStructOwnerRpc(t1val, t2val, enumVal); + } + else +#endif + { + SendTemplateStructServerRpc(t1val, t2val, enumVal); + } + } + + [Rpc(SendTo.Owner)] + private void SendTemplateStructOwnerRpc(TemplatedType t1val, TemplatedType.NestedTemplatedType t2val, TemplatedType.Enum enumVal) + { + Debug.Log($"Received server RPC values {t1val.Value} {t2val.Value1} {t2val.Value2} {enumVal}"); + t1val.Value++; + t2val.Value1++; + t2val.Value2++; + enumVal += 1; + SendTemplateStructNotOwnerRpc(t1val, t2val, enumVal); + } + + [Rpc(SendTo.NotOwner)] + private void SendTemplateStructNotOwnerRpc(TemplatedType t1val, TemplatedType.NestedTemplatedType t2val, TemplatedType.Enum enumVal) + { + Debug.Log($"Received client RPC values {t1val.Value} {t2val.Value1} {t2val.Value2}"); + OnTemplateStructUpdated?.Invoke(t1val, t2val, enumVal); } [ServerRpc(RequireOwnership = false)] @@ -885,7 +1062,6 @@ private void SendTemplateStructServerRpc(TemplatedType t1val, TemplatedType t2val.Value1++; t2val.Value2++; enumVal += 1; - SendTemplateStructClientRpc(t1val, t2val, enumVal); } @@ -895,10 +1071,39 @@ private void SendTemplateStructClientRpc(TemplatedType t1val, TemplatedType Debug.Log($"Received client RPC values {t1val.Value} {t2val.Value1} {t2val.Value2}"); OnTemplateStructUpdated?.Invoke(t1val, t2val, enumVal); } + #endregion + #region ClientStartTest(NetworkSerializableTemplatedType t1val, NetworkSerializableTemplatedType.NestedTemplatedType t2val) public void ClientStartTest(NetworkSerializableTemplatedType t1val, NetworkSerializableTemplatedType.NestedTemplatedType t2val) { - SendNetworkSerializableTemplateStructServerRpc(t1val, t2val); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendNetworkSerializableTemplateStructOwnerRpc(t1val, t2val); + } + else +#endif + { + SendNetworkSerializableTemplateStructServerRpc(t1val, t2val); + } + } + + [Rpc(SendTo.Owner)] + private void SendNetworkSerializableTemplateStructOwnerRpc(NetworkSerializableTemplatedType t1val, NetworkSerializableTemplatedType.NestedTemplatedType t2val) + { + Debug.Log($"Received NetworkSerializable server RPC values {t1val.Value} {t2val.Value1} {t2val.Value2}"); + t1val.Value++; + t2val.Value1++; + t2val.Value2++; + + SendNetworkSerializableTemplateStructNotOwnerRpc(t1val, t2val); + } + + [Rpc(SendTo.NotOwner)] + private void SendNetworkSerializableTemplateStructNotOwnerRpc(NetworkSerializableTemplatedType t1val, NetworkSerializableTemplatedType.NestedTemplatedType t2val) + { + Debug.Log($"Received NetworkSerializable client RPC values {t1val.Value} {t2val.Value1} {t2val.Value2}"); + OnNetworkSerializableTemplateStructUpdated?.Invoke(t1val, t2val); } [ServerRpc(RequireOwnership = false)] @@ -918,11 +1123,39 @@ private void SendNetworkSerializableTemplateStructClientRpc(NetworkSerializableT Debug.Log($"Received NetworkSerializable client RPC values {t1val.Value} {t2val.Value1} {t2val.Value2}"); OnNetworkSerializableTemplateStructUpdated?.Invoke(t1val, t2val); } + #endregion - + #region ClientStartTest(TemplatedType[] t1val, TemplatedType.NestedTemplatedType[] t2val, TemplatedType.Enum[] enumVal) public void ClientStartTest(TemplatedType[] t1val, TemplatedType.NestedTemplatedType[] t2val, TemplatedType.Enum[] enumVal) { - SendTemplateStructServerRpc(t1val, t2val, enumVal); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendTemplateStructOwnerRpc(t1val, t2val, enumVal); + } + else +#endif + { + SendTemplateStructServerRpc(t1val, t2val, enumVal); + } + } + + [Rpc(SendTo.Owner)] + private void SendTemplateStructOwnerRpc(TemplatedType[] t1val, TemplatedType.NestedTemplatedType[] t2val, TemplatedType.Enum[] enumVal) + { + Debug.Log($"Received server RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2} {enumVal[0]}"); + t1val[0].Value++; + t2val[0].Value1++; + t2val[0].Value2++; + enumVal[0] += 1; + SendTemplateStructNotOwnerRpc(t1val, t2val, enumVal); + } + + [Rpc(SendTo.NotOwner)] + private void SendTemplateStructNotOwnerRpc(TemplatedType[] t1val, TemplatedType.NestedTemplatedType[] t2val, TemplatedType.Enum[] enumVal) + { + Debug.Log($"Received client RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2}"); + OnTemplateStructsUpdated?.Invoke(t1val, t2val, enumVal); } [ServerRpc(RequireOwnership = false)] @@ -943,10 +1176,39 @@ private void SendTemplateStructClientRpc(TemplatedType[] t1val, TemplatedTy Debug.Log($"Received client RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2}"); OnTemplateStructsUpdated?.Invoke(t1val, t2val, enumVal); } + #endregion + #region ClientStartTest(NetworkSerializableTemplatedType[] t1val, NetworkSerializableTemplatedType.NestedTemplatedType[] t2val) public void ClientStartTest(NetworkSerializableTemplatedType[] t1val, NetworkSerializableTemplatedType.NestedTemplatedType[] t2val) { - SendNetworkSerializableTemplateStructServerRpc(t1val, t2val); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendNetworkSerializableTemplateStructOwnerRpc(t1val, t2val); + } + else +#endif + { + SendNetworkSerializableTemplateStructServerRpc(t1val, t2val); + } + } + + [Rpc(SendTo.Owner)] + private void SendNetworkSerializableTemplateStructOwnerRpc(NetworkSerializableTemplatedType[] t1val, NetworkSerializableTemplatedType.NestedTemplatedType[] t2val) + { + Debug.Log($"Received NetworkSerializable server RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2}"); + t1val[0].Value++; + t2val[0].Value1++; + t2val[0].Value2++; + + SendNetworkSerializableTemplateStructNotOwnerRpc(t1val, t2val); + } + + [Rpc(SendTo.NotOwner)] + private void SendNetworkSerializableTemplateStructNotOwnerRpc(NetworkSerializableTemplatedType[] t1val, NetworkSerializableTemplatedType.NestedTemplatedType[] t2val) + { + Debug.Log($"Received NetworkSerializable client RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2}"); + OnNetworkSerializableTemplateStructsUpdated?.Invoke(t1val, t2val); } [ServerRpc(RequireOwnership = false)] @@ -966,14 +1228,39 @@ private void SendNetworkSerializableTemplateStructClientRpc(NetworkSerializableT Debug.Log($"Received NetworkSerializable client RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2}"); OnNetworkSerializableTemplateStructsUpdated?.Invoke(t1val, t2val); } + #endregion + #region ClientStartTest(UserSerializableStruct userSerializableStruct) /// /// Starts the unit test and passes the UserSerializableStruct from the client to the server /// /// public void ClientStartTest(UserSerializableStruct userSerializableStruct) { - SendServerSerializedDataStructServerRpc(userSerializableStruct); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendServerSerializedDataStructOwnerRpc(userSerializableStruct); + } + else +#endif + { + SendServerSerializedDataStructServerRpc(userSerializableStruct); + } + } + + [Rpc(SendTo.Owner)] + private void SendServerSerializedDataStructOwnerRpc(UserSerializableStruct userSerializableStruct) + { + userSerializableStruct.MyintValue++; + userSerializableStruct.MyulongValue++; + SendClientSerializedDataStructNotOwnerRpc(userSerializableStruct); + } + + [Rpc(SendTo.NotOwner)] + private void SendClientSerializedDataStructNotOwnerRpc(UserSerializableStruct userSerializableStruct) + { + OnSerializableStructUpdated?.Invoke(userSerializableStruct); } /// @@ -998,68 +1285,152 @@ private void SendClientSerializedDataStructClientRpc(UserSerializableStruct user { OnSerializableStructUpdated?.Invoke(userSerializableStruct); } + #endregion - [ClientRpc] - public void SendMyObjectClientRpc(MyObject obj) + + + + + + #region SendMyObject + [Rpc(SendTo.NotOwner)] + public void SendMyObjectNotOwnerRpc(MyObject obj) { OnMyObjectUpdated?.Invoke(obj); } - [ClientRpc] - public void SendIntListClientRpc(List lst) + + [Rpc(SendTo.Owner)] + public void SendMyObjectOwnerRpc(MyObject obj) { - OnIntListUpdated?.Invoke(lst); + OnMyObjectUpdated?.Invoke(obj); + SendMyObjectNotOwnerRpc(obj); } + [ClientRpc] - public void SendStringListClientRpc(List lst) + public void SendMyObjectClientRpc(MyObject obj) { - OnStringListUpdated?.Invoke(lst); + OnMyObjectUpdated?.Invoke(obj); } - [ClientRpc] - public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef obj) + + [ServerRpc(RequireOwnership = false)] + public void SendMyObjectServerRpc(MyObject obj) { - OnMyObjectPassedWithThisRefUpdated?.Invoke(obj); + OnMyObjectUpdated?.Invoke(obj); + SendMyObjectClientRpc(obj); } + #endregion - [ClientRpc] - public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedById obj) + #region SendIntList + [Rpc(SendTo.NotOwner)] + public void SendIntListNotOwnerRpc(List lst) { - OnMySharedObjectReferencedByIdUpdated?.Invoke(obj); + OnIntListUpdated?.Invoke(lst); } - [ServerRpc] - public void SendMyObjectServerRpc(MyObject obj) + [Rpc(SendTo.Owner)] + public void SendIntListOwnerRpc(List lst) { - OnMyObjectUpdated?.Invoke(obj); - SendMyObjectClientRpc(obj); + OnIntListUpdated?.Invoke(lst); + SendIntListNotOwnerRpc(lst); + } + + [ClientRpc] + public void SendIntListClientRpc(List lst) + { + OnIntListUpdated?.Invoke(lst); } - [ServerRpc] + [ServerRpc(RequireOwnership = false)] public void SendIntListServerRpc(List lst) { OnIntListUpdated?.Invoke(lst); SendIntListClientRpc(lst); } + #endregion - [ServerRpc] + #region SendStringList + [Rpc(SendTo.NotOwner)] + public void SendStringListNotOwnerRpc(List lst) + { + OnStringListUpdated?.Invoke(lst); + } + + [Rpc(SendTo.Owner)] + public void SendStringListOwnerRpc(List lst) + { + OnStringListUpdated?.Invoke(lst); + SendStringListNotOwnerRpc(lst); + } + + [ClientRpc] + public void SendStringListClientRpc(List lst) + { + OnStringListUpdated?.Invoke(lst); + } + + [ServerRpc(RequireOwnership = false)] public void SendStringListServerRpc(List lst) { OnStringListUpdated?.Invoke(lst); SendStringListClientRpc(lst); } + #endregion + + #region SendMyObjectPassedWithThisRef + [Rpc(SendTo.NotOwner)] + public void SendMyObjectPassedWithThisRefNotOwnerRpc(MyObjectPassedWithThisRef obj) + { + OnMyObjectPassedWithThisRefUpdated?.Invoke(obj); + } + + [Rpc(SendTo.Owner)] + public void SendMyObjectPassedWithThisRefOwnerRpc(MyObjectPassedWithThisRef obj) + { + OnMyObjectPassedWithThisRefUpdated?.Invoke(obj); + SendMyObjectPassedWithThisRefNotOwnerRpc(obj); + } - [ServerRpc] + [ClientRpc] + public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef obj) + { + OnMyObjectPassedWithThisRefUpdated?.Invoke(obj); + } + + [ServerRpc(RequireOwnership = false)] public void SendMyObjectPassedWithThisRefServerRpc(MyObjectPassedWithThisRef obj) { OnMyObjectPassedWithThisRefUpdated?.Invoke(obj); SendMyObjectPassedWithThisRefClientRpc(obj); } + #endregion + + #region SendMySharedObjectReferencedById + [Rpc(SendTo.NotOwner)] + public void SendMySharedObjectReferencedByIdNotOwnerRpc(MySharedObjectReferencedById obj) + { + OnMySharedObjectReferencedByIdUpdated?.Invoke(obj); + } - [ServerRpc] + [Rpc(SendTo.Owner)] + public void SendMySharedObjectReferencedByIdOwnerRpc(MySharedObjectReferencedById obj) + { + OnMySharedObjectReferencedByIdUpdated?.Invoke(obj); + SendMySharedObjectReferencedByIdNotOwnerRpc(obj); + } + + [ClientRpc] + public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedById obj) + { + OnMySharedObjectReferencedByIdUpdated?.Invoke(obj); + } + + [ServerRpc(RequireOwnership = false)] public void SendMySharedObjectReferencedByIdServerRpc(MySharedObjectReferencedById obj) { OnMySharedObjectReferencedByIdUpdated?.Invoke(obj); SendMySharedObjectReferencedByIdClientRpc(obj); } + #endregion } /// @@ -1090,6 +1461,7 @@ public class TestCustomTypesArrayComponent : NetworkBehaviour public OnSerializableStructsUpdatedDelgateHandler OnSerializableStructsUpdatedServerRpc; public OnSerializableStructsUpdatedDelgateHandler OnSerializableStructsUpdatedClientRpc; + #region ClientStartTest(UserSerializableClass[] userSerializableClasses) /// /// Starts the unit test and passes the userSerializableClasses array /// from the client to the server @@ -1097,7 +1469,39 @@ public class TestCustomTypesArrayComponent : NetworkBehaviour /// public void ClientStartTest(UserSerializableClass[] userSerializableClasses) { - SendServerSerializedDataClassArryServerRpc(userSerializableClasses); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendServerSerializedDataClassArryOwnerRpc(userSerializableClasses); + } + else +#endif + { + SendServerSerializedDataClassArryServerRpc(userSerializableClasses); + } + } + + /// + /// Server receives the UserSerializableClasses array, invokes the callback + /// that checks the order, and then passes it back to the client + /// + /// + [Rpc(SendTo.Owner)] + private void SendServerSerializedDataClassArryOwnerRpc(UserSerializableClass[] userSerializableClasses) + { + OnSerializableClassesUpdatedServerRpc?.Invoke(userSerializableClasses); + SendClientSerializedDataClassArrayNotOwnerRpc(userSerializableClasses); + } + + /// + /// Client receives the UserSerializableClass array and invokes the callback + /// for verification and signaling the test is complete. + /// + /// + [Rpc(SendTo.NotOwner)] + private void SendClientSerializedDataClassArrayNotOwnerRpc(UserSerializableClass[] userSerializableClasses) + { + OnSerializableClassesUpdatedClientRpc?.Invoke(userSerializableClasses); } /// @@ -1122,7 +1526,9 @@ private void SendClientSerializedDataClassArrayClientRpc(UserSerializableClass[] { OnSerializableClassesUpdatedClientRpc?.Invoke(userSerializableClasses); } + #endregion + #region ClientStartStructTest(UserSerializableStruct[] userSerializableStructs) /// /// Starts the unit test and passes the userSerializableStructs array /// from the client to the server @@ -1130,7 +1536,39 @@ private void SendClientSerializedDataClassArrayClientRpc(UserSerializableClass[] /// public void ClientStartStructTest(UserSerializableStruct[] userSerializableStructs) { - SendServerSerializedDataStructArrayServerRpc(userSerializableStructs); +#if NGO_DAMODE + if (NetworkManager.DistributedAuthorityMode) + { + SendServerSerializedDataStructArrayOwnerRpc(userSerializableStructs); + } + else +#endif + { + SendServerSerializedDataStructArrayServerRpc(userSerializableStructs); + } + } + + /// + /// Owner receives the UserSerializableStructs array, invokes the callback + /// that checks the order, and then passes it back to the client + /// + /// + [Rpc(SendTo.Owner)] + private void SendServerSerializedDataStructArrayOwnerRpc(UserSerializableStruct[] userSerializableStructs) + { + OnSerializableStructsUpdatedServerRpc?.Invoke(userSerializableStructs); + SendClientSerializedDataStructArrayNotOwnerRpc(userSerializableStructs); + } + + /// + /// NotOwner receives the userSerializableStructs array and invokes the callback + /// for verification and signaling the test is complete. + /// + /// + [Rpc(SendTo.NotOwner)] + private void SendClientSerializedDataStructArrayNotOwnerRpc(UserSerializableStruct[] userSerializableStructs) + { + OnSerializableStructsUpdatedClientRpc?.Invoke(userSerializableStructs); } /// @@ -1155,72 +1593,149 @@ private void SendClientSerializedDataStructArrayClientRpc(UserSerializableStruct { OnSerializableStructsUpdatedClientRpc?.Invoke(userSerializableStructs); } + #endregion - [ClientRpc] - public void SendMyObjectClientRpc(MyObject[] objs) + #region SendMyObject Array + [Rpc(SendTo.NotOwner)] + public void SendMyObjectNotOwnerRpc(MyObject[] objs) { OnMyObjectUpdated?.Invoke(objs); } - [ClientRpc] - public void SendIntListClientRpc(List[] lists) + [Rpc(SendTo.Owner)] + public void SendMyObjectOwnerRpc(MyObject[] objs) { - OnIntListUpdated?.Invoke(lists); + OnMyObjectUpdated?.Invoke(objs); + SendMyObjectNotOwnerRpc(objs); } [ClientRpc] - public void SendStringListClientRpc(List[] lists) + public void SendMyObjectClientRpc(MyObject[] objs) { - OnStringListUpdated?.Invoke(lists); + OnMyObjectUpdated?.Invoke(objs); } - [ClientRpc] - public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef[] objs) + [ServerRpc(RequireOwnership = false)] + public void SendMyObjectServerRpc(MyObject[] objs) { - OnMyObjectPassedWithThisRefUpdated?.Invoke(objs); + OnMyObjectUpdated?.Invoke(objs); + SendMyObjectClientRpc(objs); } + #endregion - [ClientRpc] - public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedById[] objs) + #region SendIntList Array + [Rpc(SendTo.NotOwner)] + public void SendIntListNotOwnerRpc(List[] lists) { - OnMySharedObjectReferencedByIdUpdated?.Invoke(objs); + OnIntListUpdated?.Invoke(lists); } - [ServerRpc] - public void SendMyObjectServerRpc(MyObject[] objs) + [Rpc(SendTo.Owner)] + public void SendIntListOwnerRpc(List[] lists) { - OnMyObjectUpdated?.Invoke(objs); - SendMyObjectClientRpc(objs); + OnIntListUpdated?.Invoke(lists); + SendIntListNotOwnerRpc(lists); } - [ServerRpc] + [ClientRpc] + public void SendIntListClientRpc(List[] lists) + { + OnIntListUpdated?.Invoke(lists); + } + + [ServerRpc(RequireOwnership = false)] public void SendIntListServerRpc(List[] lists) { OnIntListUpdated?.Invoke(lists); SendIntListClientRpc(lists); } + #endregion - [ServerRpc] + #region SendStringList Array + [Rpc(SendTo.NotOwner)] + public void SendStringListNotOwnerRpc(List[] lists) + { + OnStringListUpdated?.Invoke(lists); + } + + [Rpc(SendTo.Owner)] + public void SendStringListOwnerRpc(List[] lists) + { + OnStringListUpdated?.Invoke(lists); + SendStringListNotOwnerRpc(lists); + } + + [ClientRpc] + public void SendStringListClientRpc(List[] lists) + { + OnStringListUpdated?.Invoke(lists); + } + + [ServerRpc(RequireOwnership = false)] public void SendStringListServerRpc(List[] lists) { OnStringListUpdated?.Invoke(lists); SendStringListClientRpc(lists); } + #endregion + + #region SendMyObjectPassedWithThisRef Array + [Rpc(SendTo.NotOwner)] + public void SendMyObjectPassedWithThisRefNotOwnerRpc(MyObjectPassedWithThisRef[] objs) + { + OnMyObjectPassedWithThisRefUpdated?.Invoke(objs); + } + + [Rpc(SendTo.Owner)] + public void SendMyObjectPassedWithThisRefOwnerRpc(MyObjectPassedWithThisRef[] objs) + { + OnMyObjectPassedWithThisRefUpdated?.Invoke(objs); + SendMyObjectPassedWithThisRefNotOwnerRpc(objs); + } + + [ClientRpc] + public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef[] objs) + { + OnMyObjectPassedWithThisRefUpdated?.Invoke(objs); + } - [ServerRpc] + [ServerRpc(RequireOwnership = false)] public void SendMyObjectPassedWithThisRefServerRpc(MyObjectPassedWithThisRef[] objs) { OnMyObjectPassedWithThisRefUpdated?.Invoke(objs); SendMyObjectPassedWithThisRefClientRpc(objs); } + #endregion + + #region SendMySharedObjectReferencedById Array + [Rpc(SendTo.NotOwner)] + public void SendMySharedObjectReferencedByIdNotOwnerRpc(MySharedObjectReferencedById[] objs) + { + OnMySharedObjectReferencedByIdUpdated?.Invoke(objs); + } + + [Rpc(SendTo.Owner)] + public void SendMySharedObjectReferencedByIdOwnerRpc(MySharedObjectReferencedById[] objs) + { + OnMySharedObjectReferencedByIdUpdated?.Invoke(objs); + + SendMySharedObjectReferencedByIdNotOwnerRpc(objs); + } + [ClientRpc] + public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedById[] objs) + { + OnMySharedObjectReferencedByIdUpdated?.Invoke(objs); + } - [ServerRpc] + [ServerRpc(RequireOwnership = false)] public void SendMySharedObjectReferencedByIdServerRpc(MySharedObjectReferencedById[] objs) { OnMySharedObjectReferencedByIdUpdated?.Invoke(objs); + SendMySharedObjectReferencedByIdClientRpc(objs); } + #endregion } /// diff --git a/testproject/Assets/Tests/Runtime/SenderIdTests.cs b/testproject/Assets/Tests/Runtime/SenderIdTests.cs index 92ebf2f4a1..591469145b 100644 --- a/testproject/Assets/Tests/Runtime/SenderIdTests.cs +++ b/testproject/Assets/Tests/Runtime/SenderIdTests.cs @@ -10,6 +10,10 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class SenderIdTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -17,6 +21,10 @@ public class SenderIdTests : NetcodeIntegrationTest private NetworkManager FirstClient => m_ClientNetworkManagers[0]; private NetworkManager SecondClient => m_ClientNetworkManagers[1]; +#if NGO_DAMODE + public SenderIdTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + [UnityTest] public IEnumerator WhenSendingMessageFromServerToClient_SenderIdIsCorrect() { diff --git a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs index 29ef366237..a55b943ad1 100644 --- a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs +++ b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs @@ -8,9 +8,18 @@ namespace TestProject.RuntimeTests { +#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(SessionModeTypes.ClientServer)] +#endif public class ServerDisconnectsClientTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; + +#if NGO_DAMODE + public ServerDisconnectsClientTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } +#endif + protected override void OnCreatePlayerPrefab() { m_PlayerPrefab.AddComponent(); diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 427d544b3e..b3d95ab155 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,20 +1,22 @@ { "dependencies": { - "com.unity.addressables": "1.21.9", - "com.unity.ai.navigation": "1.1.1", - "com.unity.collab-proxy": "1.17.7", - "com.unity.ide.rider": "3.0.18", - "com.unity.ide.visualstudio": "2.0.17", + "com.unity.addressables": "1.21.19", + "com.unity.ai.navigation": "1.1.5", + "com.unity.collab-proxy": "2.2.0", + "com.unity.ide.rider": "3.0.27", + "com.unity.ide.visualstudio": "2.0.22", "com.unity.mathematics": "1.2.6", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.49.0-preview", - "com.unity.services.authentication": "2.4.0", - "com.unity.services.core": "1.8.1", + "com.unity.services.authentication": "2.7.2", + "com.unity.services.core": "1.12.2", "com.unity.services.relay": "1.0.5", "com.unity.test-framework": "1.3.3", "com.unity.test-framework.performance": "2.8.1-preview", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.8.2", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", + "com.unity.transport": "2.2.1", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 5c50733307..15391f8cd1 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,11 +1,11 @@ { "dependencies": { "com.unity.addressables": { - "version": "1.21.9", + "version": "1.21.19", "depth": 0, "source": "registry", "dependencies": { - "com.unity.scriptablebuildpipeline": "1.21.3", + "com.unity.scriptablebuildpipeline": "1.21.21", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", @@ -15,7 +15,7 @@ "url": "https://packages.unity.com" }, "com.unity.ai.navigation": { - "version": "1.1.1", + "version": "1.1.5", "depth": 0, "source": "registry", "dependencies": { @@ -24,30 +24,31 @@ "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.6.6", - "depth": 2, + "version": "1.8.12", + "depth": 1, "source": "registry", "dependencies": { - "com.unity.mathematics": "1.2.1" + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "1.17.7", + "version": "2.2.0", "depth": 0, "source": "registry", - "dependencies": { - "com.unity.services.core": "1.0.1" - }, + "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "1.2.4", - "depth": 2, + "version": "2.2.1", + "depth": 1, "source": "registry", "dependencies": { - "com.unity.burst": "1.6.6", - "com.unity.test-framework": "1.1.31" + "com.unity.burst": "1.8.8", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.test-framework.performance": "3.0.2" }, "url": "https://packages.unity.com" }, @@ -59,7 +60,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.18", + "version": "3.0.27", "depth": 0, "source": "registry", "dependencies": { @@ -68,7 +69,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.17", + "version": "2.0.22", "depth": 0, "source": "registry", "dependencies": { @@ -77,8 +78,8 @@ "url": "https://packages.unity.com" }, "com.unity.mathematics": { - "version": "1.2.6", - "depth": 0, + "version": "1.3.1", + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -88,19 +89,19 @@ "depth": 0, "source": "local", "dependencies": { - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.3.4" + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.transport": "2.2.1" } }, "com.unity.nuget.mono-cecil": { - "version": "1.10.1", + "version": "1.11.4", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.nuget.newtonsoft-json": { - "version": "3.0.2", + "version": "3.2.1", "depth": 1, "source": "registry", "dependencies": {}, @@ -116,37 +117,37 @@ "url": "https://packages.unity.com" }, "com.unity.scriptablebuildpipeline": { - "version": "1.21.3", + "version": "1.21.21", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.services.authentication": { - "version": "2.4.0", + "version": "2.7.2", "depth": 0, "source": "registry", "dependencies": { - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.services.core": "1.7.0", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.services.core": "1.10.1", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.ugui": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.core": { - "version": "1.8.1", + "version": "1.12.2", "depth": 0, "source": "registry", "dependencies": { "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.modules.androidjni": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.qos": { - "version": "1.1.0", + "version": "1.2.1", "depth": 1, "source": "registry", "dependencies": { @@ -176,6 +177,22 @@ }, "url": "https://packages.unity.com" }, + "com.unity.sysroot": { + "version": "2.0.7", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.sysroot.linux-x86_64": { + "version": "2.0.6", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7" + }, + "url": "https://packages.unity.com" + }, "com.unity.test-framework": { "version": "1.3.3", "depth": 0, @@ -188,11 +205,11 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework.performance": { - "version": "2.8.1-preview", - "depth": 0, + "version": "3.0.2", + "depth": 2, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.0", + "com.unity.test-framework": "1.1.31", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" @@ -218,14 +235,24 @@ }, "url": "https://packages.unity.com" }, + "com.unity.toolchain.win-x86_64-linux-x86_64": { + "version": "2.0.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7", + "com.unity.sysroot.linux-x86_64": "2.0.6" + }, + "url": "https://packages.unity.com" + }, "com.unity.transport": { - "version": "1.3.4", - "depth": 1, + "version": "2.2.1", + "depth": 0, "source": "registry", "dependencies": { - "com.unity.collections": "1.2.4", - "com.unity.burst": "1.6.6", - "com.unity.mathematics": "1.2.6" + "com.unity.collections": "2.2.1", + "com.unity.burst": "1.8.8", + "com.unity.mathematics": "1.3.1" }, "url": "https://packages.unity.com" }, @@ -370,17 +397,6 @@ "version": "1.0.0", "depth": 0, "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.uielementsnative": "1.0.0" - } - }, - "com.unity.modules.uielementsnative": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index fb7343c721..553549cc12 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -89,9 +89,6 @@ EditorBuildSettings: - enabled: 1 path: Assets/Samples/Physics/PhysicsSample.unity guid: 2c76877ad66aa22458c62a0d74514a91 - - enabled: 1 - path: Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity - guid: 7522f8723c5674939a3c24acfd83a688 - enabled: 1 path: Assets/Tests/Manual/IntegrationTestScenes/UnitTestBaseScene.unity guid: fa0b4956a4a0aee48ae43f9116d28354 @@ -110,9 +107,6 @@ EditorBuildSettings: - enabled: 1 path: Assets/Samples/Teleport/TeleportSample.unity guid: efa247d1f78ca694f8d2dcb5672e8f8b - - enabled: 1 - path: Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity - guid: 49fd14bff1eceda4f9299721a9029750 - enabled: 1 path: Assets/Tests/Manual/NestedNetworkTransforms/NestedNetworkTransforms.unity guid: 92b8cccf28cbaba40854a025b66e2ac3 @@ -125,6 +119,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/NestedNetworkTransforms/LerpVsSlerpScene.unity guid: 54ca4944ec2b95640a68bc35403a4977 + - enabled: 1 + path: Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjects.unity + guid: d04d395e163705441935990f702c782f - enabled: 1 path: Assets/Tests/Manual/IntegrationTestScenes/GenericInScenePlacedObject.unity guid: 43c36dc1d38660e4d9879e84e580e22f @@ -143,6 +140,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/PrefabTestAssets/PrefabTestScene.unity guid: 6449955dcdde54944ba1cdb97a23bd29 + - enabled: 1 + path: Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity + guid: abe92ece17d830e41a66dff7edc9245d m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index dd7e0b04f1..307ccd3516 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2020.3.40f1 -m_EditorVersionWithRevision: 2020.3.40f1 (ba48d4efcef1) +m_EditorVersion: 2022.3.19f1 +m_EditorVersionWithRevision: 2022.3.19f1 (244b723c30a6) From 67f4a62edb3b3c1a762cae328d2ee4288be2bcf5 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 13:15:23 -0500 Subject: [PATCH 002/236] update Adding project settings --- testproject/ProjectSettings/ProjectSettings.asset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index fbf187f5a1..f2d38f6dd6 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -51,7 +51,7 @@ PlayerSettings: m_MTRendering: 1 mipStripping: 0 numberOfMipsStripped: 0 - m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + m_StackTraceTypes: 010000000100000000000000000000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 @@ -580,7 +580,7 @@ PlayerSettings: webGLThreadsSupport: 0 webGLDecompressionFallback: 0 scriptingDefineSymbols: - 1: DEBUG_NETWORKTRANSFORM;UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + 1: NGO_DAMODE;UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT additionalCompilerArguments: 1: - -warnaserror From 41d13bc4cd7c689de8a112d9df5fc2fd9143d370 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 13:25:43 -0500 Subject: [PATCH 003/236] update adding missing meta files and removing migrated class. --- .../Messaging/Messages/SessionOwnerMessage.cs.meta | 2 +- .../NetworkObjectOwnershipPropertiesTests.cs.meta | 11 +++++++++++ .../ObjectParenting/ReparentingCubeNetBhv.cs | 13 ------------- 3 files changed, 12 insertions(+), 14 deletions(-) rename testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs.meta => com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs.meta (83%) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs.meta delete mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs.meta similarity index 83% rename from testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs.meta index 3d405245c3..832da7f253 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs.meta +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 9b9fb5b91091c4036bb4b0321b79f6c8 +guid: 8e02985965397ab44809c99406914311 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs.meta new file mode 100644 index 0000000000..5a555c06a6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c88cc36139c1574fba571485477d642 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs deleted file mode 100644 index cea4f497e4..0000000000 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ReparentingCubeNetBhv.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Unity.Netcode; -namespace TestProject.RuntimeTests -{ - public class ReparentingCubeNetBhv : NetworkBehaviour - { - public NetworkObject ParentNetworkObject { get; private set; } - - public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) - { - ParentNetworkObject = parentNetworkObject; - } - } -} From 20d1cc1143039ee05a867f5002841d19cf065f2b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 13:40:20 -0500 Subject: [PATCH 004/236] update removing duplicate classes --- .../ParentingAutoSyncManager.cs | 64 ------------------- .../ParentingAutoSyncManager.cs.meta | 11 ---- 2 files changed, 75 deletions(-) delete mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs delete mode 100644 testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs deleted file mode 100644 index bd32c3f539..0000000000 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using Unity.Netcode; -using UnityEngine; - - -namespace TestProject.RuntimeTests -{ - /// - /// Helper class that builds 4 transform lists based on parent-child hierarchy - /// for the - /// - public class ParentingAutoSyncManager : NetworkBehaviour - { - public static ParentingAutoSyncManager ServerInstance; - public static Dictionary ClientInstances = new Dictionary(); - - public GameObject WithNetworkObjectAutoSyncOn; - public GameObject WithNetworkObjectAutoSyncOff; - public GameObject GameObjectAutoSyncOn; - public GameObject GameObjectAutoSyncOff; - - public List NetworkObjectAutoSyncOnTransforms = new List(); - public List NetworkObjectAutoSyncOffTransforms = new List(); - public List GameObjectAutoSyncOnTransforms = new List(); - public List GameObjectAutoSyncOffTransforms = new List(); - - public static void Reset() - { - ServerInstance = null; - ClientInstances.Clear(); - } - - public override void OnNetworkSpawn() - { - if (IsServer) - { - ServerInstance = this; - } - else - { - ClientInstances.Add(NetworkManager.LocalClientId, this); - } - var currentRoot = WithNetworkObjectAutoSyncOn.transform; - NetworkObjectAutoSyncOnTransforms.Add(currentRoot); - NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0)); - NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0)); - - currentRoot = WithNetworkObjectAutoSyncOff.transform; - NetworkObjectAutoSyncOffTransforms.Add(currentRoot); - NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0)); - NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0)); - - currentRoot = GameObjectAutoSyncOn.transform; - GameObjectAutoSyncOnTransforms.Add(currentRoot); - GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0)); - GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0)); - - currentRoot = GameObjectAutoSyncOff.transform; - GameObjectAutoSyncOffTransforms.Add(currentRoot); - GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0)); - GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0)); - } - } -} diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta deleted file mode 100644 index d147ac87ad..0000000000 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingAutoSyncManager.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3326399105afcf4459b51991aeeaacd2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 552a37c944515ddafd720137f74eec7789616f68 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 14:56:35 -0500 Subject: [PATCH 005/236] update Reverting manifest and packages-lock updates. --- testproject/Packages/manifest.json | 16 ++-- testproject/Packages/packages-lock.json | 112 ++++++++++-------------- 2 files changed, 55 insertions(+), 73 deletions(-) diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index b3d95ab155..427d544b3e 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,22 +1,20 @@ { "dependencies": { - "com.unity.addressables": "1.21.19", - "com.unity.ai.navigation": "1.1.5", - "com.unity.collab-proxy": "2.2.0", - "com.unity.ide.rider": "3.0.27", - "com.unity.ide.visualstudio": "2.0.22", + "com.unity.addressables": "1.21.9", + "com.unity.ai.navigation": "1.1.1", + "com.unity.collab-proxy": "1.17.7", + "com.unity.ide.rider": "3.0.18", + "com.unity.ide.visualstudio": "2.0.17", "com.unity.mathematics": "1.2.6", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.49.0-preview", - "com.unity.services.authentication": "2.7.2", - "com.unity.services.core": "1.12.2", + "com.unity.services.authentication": "2.4.0", + "com.unity.services.core": "1.8.1", "com.unity.services.relay": "1.0.5", "com.unity.test-framework": "1.3.3", "com.unity.test-framework.performance": "2.8.1-preview", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.8.2", - "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", - "com.unity.transport": "2.2.1", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 15391f8cd1..5c50733307 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,11 +1,11 @@ { "dependencies": { "com.unity.addressables": { - "version": "1.21.19", + "version": "1.21.9", "depth": 0, "source": "registry", "dependencies": { - "com.unity.scriptablebuildpipeline": "1.21.21", + "com.unity.scriptablebuildpipeline": "1.21.3", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", @@ -15,7 +15,7 @@ "url": "https://packages.unity.com" }, "com.unity.ai.navigation": { - "version": "1.1.5", + "version": "1.1.1", "depth": 0, "source": "registry", "dependencies": { @@ -24,31 +24,30 @@ "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.8.12", - "depth": 1, + "version": "1.6.6", + "depth": 2, "source": "registry", "dependencies": { - "com.unity.mathematics": "1.2.1", - "com.unity.modules.jsonserialize": "1.0.0" + "com.unity.mathematics": "1.2.1" }, "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "2.2.0", + "version": "1.17.7", "depth": 0, "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.services.core": "1.0.1" + }, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "2.2.1", - "depth": 1, + "version": "1.2.4", + "depth": 2, "source": "registry", "dependencies": { - "com.unity.burst": "1.8.8", - "com.unity.modules.unityanalytics": "1.0.0", - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.test-framework.performance": "3.0.2" + "com.unity.burst": "1.6.6", + "com.unity.test-framework": "1.1.31" }, "url": "https://packages.unity.com" }, @@ -60,7 +59,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.27", + "version": "3.0.18", "depth": 0, "source": "registry", "dependencies": { @@ -69,7 +68,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.22", + "version": "2.0.17", "depth": 0, "source": "registry", "dependencies": { @@ -78,8 +77,8 @@ "url": "https://packages.unity.com" }, "com.unity.mathematics": { - "version": "1.3.1", - "depth": 1, + "version": "1.2.6", + "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -89,19 +88,19 @@ "depth": 0, "source": "local", "dependencies": { - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.2.1" + "com.unity.nuget.mono-cecil": "1.10.1", + "com.unity.transport": "1.3.4" } }, "com.unity.nuget.mono-cecil": { - "version": "1.11.4", + "version": "1.10.1", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.nuget.newtonsoft-json": { - "version": "3.2.1", + "version": "3.0.2", "depth": 1, "source": "registry", "dependencies": {}, @@ -117,37 +116,37 @@ "url": "https://packages.unity.com" }, "com.unity.scriptablebuildpipeline": { - "version": "1.21.21", + "version": "1.21.3", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.services.authentication": { - "version": "2.7.2", + "version": "2.4.0", "depth": 0, "source": "registry", "dependencies": { - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.services.core": "1.10.1", + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.services.core": "1.7.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.ugui": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.core": { - "version": "1.12.2", + "version": "1.8.1", "depth": 0, "source": "registry", "dependencies": { "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.modules.androidjni": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.qos": { - "version": "1.2.1", + "version": "1.1.0", "depth": 1, "source": "registry", "dependencies": { @@ -177,22 +176,6 @@ }, "url": "https://packages.unity.com" }, - "com.unity.sysroot": { - "version": "2.0.7", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot.linux-x86_64": { - "version": "2.0.6", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.7" - }, - "url": "https://packages.unity.com" - }, "com.unity.test-framework": { "version": "1.3.3", "depth": 0, @@ -205,11 +188,11 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework.performance": { - "version": "3.0.2", - "depth": 2, + "version": "2.8.1-preview", + "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.31", + "com.unity.test-framework": "1.1.0", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" @@ -235,24 +218,14 @@ }, "url": "https://packages.unity.com" }, - "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.6", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.7", - "com.unity.sysroot.linux-x86_64": "2.0.6" - }, - "url": "https://packages.unity.com" - }, "com.unity.transport": { - "version": "2.2.1", - "depth": 0, + "version": "1.3.4", + "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "2.2.1", - "com.unity.burst": "1.8.8", - "com.unity.mathematics": "1.3.1" + "com.unity.collections": "1.2.4", + "com.unity.burst": "1.6.6", + "com.unity.mathematics": "1.2.6" }, "url": "https://packages.unity.com" }, @@ -397,6 +370,17 @@ "version": "1.0.0", "depth": 0, "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.uielementsnative": "1.0.0" + } + }, + "com.unity.modules.uielementsnative": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", From bcbbb61f90c974c9feac124b6fc0c1f76667a1bf Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 15:29:15 -0500 Subject: [PATCH 006/236] update and style fixing typo. Updating the minimal CI editor to use --- .yamato/project.metafile | 12 +++++------- .../Runtime/Configuration/NetworkConfig.cs | 2 +- .../Runtime/Core/NetworkManager.cs | 1 + 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index c0ec992fc0..c4df701032 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -1,5 +1,5 @@ -validation_editor: 2021.3 -mobile_validation_editor: 2021.3 +validation_editor: 2022.3 +mobile_validation_editor: 2022.3 # Platforms that will be tested. The first entry in this array will also # be used for validation @@ -42,8 +42,7 @@ projects: - name: com.unity.netcode.gameobjects path: com.unity.netcode.gameobjects test_editors: - - 2021.3 - - 2022.2 + - 2022.3 - 2023.1 - trunk - name: minimalproject @@ -55,15 +54,14 @@ projects: - name: com.unity.netcode.gameobjects path: com.unity.netcode.gameobjects test_editors: - - 2021.3 + - 2022.3 - name: testproject-tools-integration path: testproject-tools-integration validate: false publish: false has_tests: true test_editors: - - 2021.3 - - 2022.2 + - 2022.3 - trunk # Package dependencies diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 92f49495fa..832f0916f8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -152,7 +152,7 @@ public class NetworkConfig #if NGO_DAMODE - [Tooltip("Determines if the network session will run in clinet-server or distributed authority mode.")] + [Tooltip("Determines if the network session will run in client-server or distributed authority mode.")] public SessionModeTypes SessionMode; [HideInInspector] diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b904c4b16d..44d9788609 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -952,6 +952,7 @@ public int MaximumFragmentedMessageSize internal void Initialize(bool server) { #if NGO_DAMODE + // TODO: Remove this before finalizing the experimental release NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode; #endif From 1fda8ee20f55b0b7c18a738a3dbde4ef6a5476cd Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 16:12:48 -0500 Subject: [PATCH 007/236] update --- com.unity.netcode.gameobjects/ValidationExceptions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/ValidationExceptions.json b/com.unity.netcode.gameobjects/ValidationExceptions.json index 8db7cad09c..554017dd61 100644 --- a/com.unity.netcode.gameobjects/ValidationExceptions.json +++ b/com.unity.netcode.gameobjects/ValidationExceptions.json @@ -3,7 +3,7 @@ { "ValidationTest": "API Validation", "ExceptionMessage": "Breaking changes require a new major version.", - "PackageVersion": "1.8.0" + "PackageVersion": "2.0.0-exp" } ], "WarningExceptions": [] From e030a5923a09a406db12ed186722fdd4b9d9e0f9 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 16:13:25 -0500 Subject: [PATCH 008/236] update --- com.unity.netcode.gameobjects/ValidationExceptions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/ValidationExceptions.json b/com.unity.netcode.gameobjects/ValidationExceptions.json index 8db7cad09c..554017dd61 100644 --- a/com.unity.netcode.gameobjects/ValidationExceptions.json +++ b/com.unity.netcode.gameobjects/ValidationExceptions.json @@ -3,7 +3,7 @@ { "ValidationTest": "API Validation", "ExceptionMessage": "Breaking changes require a new major version.", - "PackageVersion": "1.8.0" + "PackageVersion": "2.0.0-exp" } ], "WarningExceptions": [] From 8a9a8527cb8e82e3e090d8f4401b58b714f19fdf Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 25 Mar 2024 17:05:47 -0500 Subject: [PATCH 009/236] Update package.json Forgot to add the .1 to the end of exp. --- com.unity.netcode.gameobjects/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index bd8e20aeb1..3db90c2849 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0-exp", + "version": "2.0.0-exp.1", "unity": "2022.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From e3225448ccad0fd09daaee63b1a842ec767fb808 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 25 Mar 2024 17:07:01 -0500 Subject: [PATCH 010/236] Update ValidationExceptions.json removing the error exception --- .../ValidationExceptions.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/ValidationExceptions.json b/com.unity.netcode.gameobjects/ValidationExceptions.json index 554017dd61..9128292b92 100644 --- a/com.unity.netcode.gameobjects/ValidationExceptions.json +++ b/com.unity.netcode.gameobjects/ValidationExceptions.json @@ -1,10 +1,4 @@ { - "ErrorExceptions": [ - { - "ValidationTest": "API Validation", - "ExceptionMessage": "Breaking changes require a new major version.", - "PackageVersion": "2.0.0-exp" - } - ], + "ErrorExceptions": [], "WarningExceptions": [] -} \ No newline at end of file +} From a1c5d1dc32451770103449b5a6d74566bd4c93c4 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 18:46:41 -0500 Subject: [PATCH 011/236] update Removing NGO_DAMODE --- .../Messages/NetworkTransformMessage.cs | 28 +- .../Components/NetworkAnimator.cs | 49 +-- .../Components/NetworkRigidbody.cs | 13 - .../Components/NetworkTransform.cs | 89 +--- .../RigidbodyContactEventManager.cs | 2 - .../Editor/NetworkManagerEditor.cs | 8 - .../Editor/NetworkObjectEditor.cs | 5 +- .../Runtime/Configuration/NetworkConfig.cs | 4 - .../Runtime/Connection/NetworkClient.cs | 8 - .../Connection/NetworkConnectionManager.cs | 60 +-- .../Runtime/Core/NetworkBehaviourUpdater.cs | 6 - .../Runtime/Core/NetworkManager.cs | 42 +- .../Runtime/Core/NetworkObject.cs | 215 +--------- .../Runtime/Logging/NetworkLog.cs | 17 - .../Messaging/DeferredMessageManager.cs | 19 - .../IDeferredNetworkMessageManager.cs | 4 - .../Messages/ChangeOwnershipMessage.cs | 47 +-- .../Messages/ClientConnectedMessage.cs | 10 - .../Messages/ClientDisconnectedMessage.cs | 2 - .../Messages/ConnectionApprovedMessage.cs | 25 +- .../Messages/ConnectionRequestMessage.cs | 13 +- .../Messaging/Messages/CreateObjectMessage.cs | 40 -- .../Messages/DestroyObjectMessage.cs | 21 +- .../Messages/NetworkVariableDeltaMessage.cs | 30 +- .../Messaging/Messages/ParentSyncMessage.cs | 13 +- .../Messaging/Messages/ProxyMessage.cs | 2 - .../Runtime/Messaging/Messages/RpcMessages.cs | 25 +- .../Messaging/Messages/ServerLogMessage.cs | 15 - .../Messaging/Messages/SessionOwnerMessage.cs | 3 - .../Messaging/NetworkMessageManager.cs | 4 - .../RpcTargets/AuthorityRpcTarget.cs | 2 - .../RpcTargets/NotAuthorityRpcTarget.cs | 2 - .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 8 - .../Messaging/RpcTargets/ServerRpcTarget.cs | 4 +- .../Collections/NetworkList.cs | 36 +- .../NetworkVariable/NetworkVariable.cs | 11 +- .../NetworkVariable/NetworkVariableBase.cs | 17 +- .../NetworkVariableSerialization.cs | 10 +- .../DefaultSceneManagerHandler.cs | 6 - .../SceneManagement/ISceneManagerHandler.cs | 3 +- .../SceneManagement/NetworkSceneManager.cs | 385 +----------------- .../Runtime/SceneManagement/SceneEventData.cs | 154 +------ .../SceneManagement/SceneEventProgress.cs | 12 - .../Runtime/Spawning/NetworkSpawnManager.cs | 218 +--------- .../Runtime/Timing/IRealTimeProvider.cs | 2 - .../Runtime/Timing/RealTimeProvider.cs | 2 - .../Runtime/IntegrationTestSceneHandler.cs | 2 - .../IntegrationTestWithApproximation.cs | 2 - .../TestHelpers/Runtime/MockTimeProvider.cs | 6 +- .../Runtime/NetcodeIntegrationTest.cs | 62 --- .../Tests/Runtime/DeferredMessagingTests.cs | 10 - .../DeferredDespawningTests.cs | 2 - .../DistributeObjectsTests.cs | 2 - .../DistributedAuthorityCodecTests.cs | 2 - .../NetworkClientAndPlayerObjectTests.cs | 2 - .../OwnershipPermissionsTests.cs | 2 - .../Tests/Runtime/ListChangedTest.cs | 4 - .../Runtime/NetworkBehaviourUpdaterTests.cs | 21 +- .../NetworkObjectDestroyTests.cs | 24 -- .../NetworkObjectDontDestroyWithOwnerTests.cs | 4 - .../NetworkObjectOnNetworkDespawnTests.cs | 5 - .../NetworkObjectOnSpawnTests.cs | 6 - .../NetworkObjectOwnershipPropertiesTests.cs | 32 +- .../NetworkObjectOwnershipTests.cs | 13 +- .../NetworkObjectSpawnManyObjectsTests.cs | 5 +- .../NetworkObjectSynchronizationTests.cs | 23 +- .../Tests/Runtime/NetworkShowHideTests.cs | 13 +- .../Tests/Runtime/NetworkSpawnManagerTests.cs | 10 +- .../NetworkTransformOwnershipTests.cs | 27 +- .../NetworkTransformPacketLossTests.cs | 2 - .../NetworkTransform/NetworkTransformTests.cs | 2 - .../Tests/Runtime/NetworkVarBufferCopyTest.cs | 23 -- .../Tests/Runtime/NetworkVariableTests.cs | 6 +- .../Tests/Runtime/NetworkVisibilityTests.cs | 9 - .../Tests/Runtime/OwnerModifiedTests.cs | 3 +- .../Runtime/Physics/NetworkRigidbodyTest.cs | 18 +- .../Tests/Runtime/PlayerObjectTests.cs | 8 - .../Tests/Runtime/UniversalRpcTests.cs | 74 +--- .../InSceneParentChildHandler.cs | 8 - .../ParentPlayerToInSceneNetworkObject.cs | 25 -- .../Assets/Tests/Runtime/AddressablesTests.cs | 3 - .../Runtime/Animation/NetworkAnimatorTests.cs | 2 - .../ClientSynchronizationValidationTest.cs | 6 +- .../InScenePlacedNetworkObjectTests.cs | 5 +- .../NetworkSceneManagerEventCallbacks.cs | 2 - .../NetworkSceneManagerEventDataPoolTest.cs | 2 - .../NetworkSceneManagerEventNotifications.cs | 2 - ...NetworkSceneManagerPopulateInSceneTests.cs | 5 +- .../NetworkSceneManagerSeneVerification.cs | 2 - .../NetworkSceneManagerUsageTests.cs | 4 - .../SceneEventProgressTests.cs | 4 - .../NetworkObjectParentingTests.cs | 7 +- .../ParentDynamicUnderInScenePlaced.cs | 15 +- .../ParentingInSceneObjectsTests.cs | 5 +- .../ParentingWorldPositionStaysTests.cs | 4 - .../RespawnInSceneObjectsAfterShutdown.cs | 4 - .../Assets/Tests/Runtime/SenderIdTests.cs | 5 +- .../Runtime/ServerDisconnectsClientTest.cs | 5 +- 98 files changed, 114 insertions(+), 2116 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs index 0ff0a42959..e61760d1c3 100644 --- a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs @@ -11,7 +11,6 @@ internal struct NetworkTransformMessage : INetworkMessage public int Version => 0; public ulong NetworkObjectId; public int NetworkBehaviourId; -#if NGO_DAMODE // This is only used when serializing but not serialized public bool DistributedAuthorityMode; // Might get removed @@ -25,7 +24,7 @@ private int GetTargetIdLength() } return 0; } -#endif + public NetworkTransform.NetworkTransformState State; private NetworkTransform m_ReceiverNetworkTransform; @@ -47,7 +46,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, NetworkBehaviourId); writer.WriteNetworkSerializable(State); -#if NGO_DAMODE if (DistributedAuthorityMode) { var length = GetTargetIdLength(); @@ -62,7 +60,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValuePacked(writer, target); } } -#endif } } @@ -77,12 +74,9 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int var currentPosition = reader.Position; ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); var isSpawnedLocally = networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId); -#if NGO_DAMODE + // Only defer if the NetworkObject is not spawned yet and the local NetworkManager is not running as a DAHost. if (!isSpawnedLocally && !networkManager.DAHost) -#else - if (!isSpawnedLocally) -#endif { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); return false; @@ -101,7 +95,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int // Deserialize the state reader.ReadNetworkSerializableInPlace(ref State); -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { var targetCount = 0; @@ -117,7 +110,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int TargetIds[i] = targetId; } } -#endif if (isSpawnedLocally) { @@ -127,13 +119,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int isServerAuthoritative = m_ReceiverNetworkTransform.IsServerAuthoritative(); ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer; } -#if NGO_DAMODE else { // If we are the DAHost and the NetworkObject is hidden from the host we still need to forward this message ownerAuthoritativeServerSide = networkManager.DAHost && !isSpawnedLocally; } -#endif if (ownerAuthoritativeServerSide) { @@ -149,28 +139,23 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return true; } } -#if NGO_DAMODE else if (networkManager.DAHost) { // Specific to distributed authority mode, the only sender of state updates will be the owner ownerClientId = context.SenderId; } -#endif var networkDelivery = State.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; // Forward the state update if there are any remote clients to foward it to if (networkManager.ConnectionManager.ConnectedClientsList.Count > (networkManager.IsHost ? 2 : 1)) { -#if NGO_DAMODE var clientCount = networkManager.DistributedAuthorityMode ? GetTargetIdLength() : networkManager.ConnectionManager.ConnectedClientsList.Count; if (clientCount == 0) { return true; } -#else - var clientCount = networkManager.ConnectionManager.ConnectedClientsList.Count; -#endif + // This is only to copy the existing and already serialized struct for forwarding purposes only. // This will not include any changes made to this struct at this particular stage of processing the message. var currentMessage = this; @@ -182,14 +167,9 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int for (int i = 0; i < clientCount; i++) { -#if NGO_DAMODE var clientId = networkManager.DistributedAuthorityMode ? TargetIds[i] : networkManager.ConnectionManager.ConnectedClientsList[i].ClientId; if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) || (!networkManager.DistributedAuthorityMode && !networkObject.Observers.Contains(clientId))) -#else - var clientId = networkManager.ConnectionManager.ConnectedClientsList[i].ClientId; - if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) || !networkObject.Observers.Contains(clientId)) -#endif { continue; } @@ -212,7 +192,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { -#if NGO_DAMODE var networkManager = context.SystemOwner as NetworkManager; // Only if the local NetworkManager instance is running as the DAHost we just exit if there is no local // NetworkTransform component to apply the state update to (i.e. it is hidden from the DAHost and it @@ -221,7 +200,6 @@ public void Handle(ref NetworkContext context) { return; } -#endif if (m_ReceiverNetworkTransform == null) { diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs index 2f5cea5cfe..73555e2cd8 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs @@ -25,13 +25,12 @@ private void FlushMessages() { foreach (var animationUpdate in m_SendAnimationUpdates) { -#if NGO_DAMODE + if (m_NetworkAnimator.NetworkManager.DistributedAuthorityMode) { m_NetworkAnimator.SendAnimStateRpc(animationUpdate.AnimationMessage); } else -#endif { m_NetworkAnimator.SendAnimStateClientRpc(animationUpdate.AnimationMessage, animationUpdate.ClientRpcParams); } @@ -41,13 +40,11 @@ private void FlushMessages() foreach (var sendEntry in m_SendParameterUpdates) { -#if NGO_DAMODE if (m_NetworkAnimator.NetworkManager.DistributedAuthorityMode) { m_NetworkAnimator.SendParametersUpdateRpc(sendEntry.ParametersUpdateMessage); } else -#endif { m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams); } @@ -56,13 +53,11 @@ private void FlushMessages() foreach (var sendEntry in m_SendTriggerUpdates) { -#if NGO_DAMODE if (m_NetworkAnimator.NetworkManager.DistributedAuthorityMode) { m_NetworkAnimator.SendAnimTriggerRpc(sendEntry.AnimationTriggerMessage); } else -#endif { if (!sendEntry.SendToServer) { @@ -932,13 +927,11 @@ internal void CheckForAnimatorChanges() // Send an AnimationMessage only if there are dirty AnimationStates to send if (m_AnimationMessage.IsDirtyCount > 0) { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { SendAnimStateRpc(m_AnimationMessage); } else -#endif if (!IsServer && IsOwner) { SendAnimStateServerRpc(m_AnimationMessage); @@ -963,7 +956,6 @@ private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, boo { Parameters = m_ParameterWriter.ToArray() }; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { if (IsOwner) @@ -993,23 +985,6 @@ private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, boo } } } -#else - if (!IsServer) - { - SendParametersUpdateServerRpc(parametersMessage); - } - else - { - if (sendDirect) - { - SendParametersUpdateClientRpc(parametersMessage, clientRpcParams); - } - else - { - m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersMessage, clientRpcParams); - } - } -#endif } /// @@ -1286,7 +1261,6 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame } } -#if NGO_DAMODE /// /// Distributed Authority: Updates the client's animator's parameters /// @@ -1295,7 +1269,6 @@ internal void SendParametersUpdateRpc(ParametersUpdateMessage parametersUpdate) { m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate); } -#endif /// /// Client-Server: Updates the client's animator's parameters @@ -1354,7 +1327,6 @@ internal void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRp ProcessAnimStates(animationMessage); } -#if NGO_DAMODE /// /// Distributed Authority: Internally-called RPC non-authority receiving function to update animation states /// @@ -1363,11 +1335,9 @@ internal void SendAnimStateRpc(AnimationMessage animationMessage) { ProcessAnimStates(animationMessage); } -#endif private void ProcessAnimStates(AnimationMessage animationMessage) { -#if NGO_DAMODE if (HasAuthority) { if (NetworkManager.LogLevel == LogLevel.Developer) @@ -1378,16 +1348,7 @@ private void ProcessAnimStates(AnimationMessage animationMessage) } return; } -#else - if (NetworkManager.IsHost) - { - if (NetworkManager.LogLevel == LogLevel.Developer) - { - NetworkLog.LogWarning("Detected the Host is sending itself animation updates! Please report this issue."); - } - return; - } -#endif + foreach (var animationState in animationMessage.AnimationStates) { UpdateAnimationState(animationState); @@ -1439,7 +1400,6 @@ private void InternalSetTrigger(int hash, bool isSet = true) m_Animator.SetBool(hash, isSet); } -#if NGO_DAMODE /// /// Distributed Authority: Internally-called RPC client receiving function to update a trigger when the server wants to forward /// a trigger for a client to play / reset @@ -1450,7 +1410,6 @@ internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage { InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); } -#endif /// /// Client Server: Internally-called RPC client receiving function to update a trigger when the server wants to forward @@ -1491,16 +1450,12 @@ public void SetTrigger(int hash, bool setTrigger = true) // will happen when SendAnimTriggerClientRpc is called. For a client owner, we call the // SendAnimTriggerServerRpc and then trigger locally when running in owner authority mode. var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger }; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode && HasAuthority) { m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage); InternalSetTrigger(hash, setTrigger); } else if (!NetworkManager.DistributedAuthorityMode && (IsOwner || IsServer)) -#else - if (IsOwner || IsServer) -#endif { if (IsServer) { diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs index 5ebca355f4..3ea8ff0487 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs @@ -12,10 +12,7 @@ namespace Unity.Netcode.Components [AddComponentMenu("Netcode/Network Rigidbody")] public class NetworkRigidbody : NetworkBehaviour { -#if NGO_DAMODE public bool UseRigidBodyForMotion; -#endif - private Rigidbody m_Rigidbody; private NetworkTransform m_NetworkTransform; private RigidbodyInterpolation m_OriginalInterpolation; @@ -43,12 +40,10 @@ protected virtual void Awake() private void SetupRigidbody() { m_OriginalInterpolation = m_Rigidbody.interpolation; -#if NGO_DAMODE if (m_NetworkTransform != null) { m_NetworkTransform.RegisterRigidbody(this, m_Rigidbody); } -#endif m_Rigidbody.isKinematic = true; } @@ -70,7 +65,6 @@ public override void OnLostOwnership() UpdateOwnershipAuthority(); } -#if NGO_DAMODE protected override void OnOwnershipChanged(ulong previous, ulong current) { if (NetworkManager.LocalClientId == current || NetworkManager.LocalClientId == previous) @@ -79,7 +73,6 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) } base.OnOwnershipChanged(previous, current); } -#endif /// /// Sets the authority differently depending upon @@ -87,14 +80,12 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) /// internal void UpdateOwnershipAuthority() { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { // When in distributed authority mode, always use HasAuthority m_IsAuthority = HasAuthority; } else -#endif { if (m_NetworkTransform.IsServerAuthoritative()) { @@ -108,7 +99,6 @@ internal void UpdateOwnershipAuthority() m_Rigidbody.isKinematic = !m_IsAuthority; -#if NGO_DAMODE if (UseRigidBodyForMotion) { if (m_Rigidbody.isKinematic) @@ -132,7 +122,6 @@ internal void UpdateOwnershipAuthority() } } else -#endif { m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation); } @@ -155,7 +144,6 @@ public override void OnNetworkDespawn() m_Rigidbody.interpolation = m_OriginalInterpolation; } -#if NGO_DAMODE /// /// When using with a and is /// enabled, the will update Kinematic instances using the move methods. @@ -173,7 +161,6 @@ private void FixedUpdate() } m_NetworkTransform.OnFixedUpdate(); } -#endif } } #endif // COM_UNITY_MODULES_PHYSICS diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 3ddadb3369..6e5dfc856a 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -3,9 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using Unity.Mathematics; -#if NGO_DAMODE using Unity.Netcode.Transports.UTP; -#endif using UnityEngine; namespace Unity.Netcode.Components @@ -1338,7 +1336,7 @@ internal NetworkTransformState LocalAuthoritativeNetworkState private Vector3 m_TargetScale; private Quaternion m_CurrentRotation; private Vector3 m_TargetRotation; -#if NGO_DAMODE + private bool m_UseRigidbodyForMotion; private Rigidbody m_RigidbodyInternal; private NetworkRigidbody m_NetworkRigidbody; @@ -1352,8 +1350,6 @@ internal void RegisterRigidbody(NetworkRigidbody networkRigidbody, Rigidbody rig m_RigidbodyInternal = rigidbody; } } -#endif - #if DEBUG_NETWORKTRANSFORM || UNITY_INCLUDE_TESTS /// @@ -1411,13 +1407,8 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) { if (!IsServerAuthoritative() && NetworkObject.OwnerClientId == targetClientId) { -#if NGO_DAMODE // In distributed authority mode we want to synchronize the half float if we are the owner. return (!NetworkManager.DistributedAuthorityMode && NetworkObject.IsOwnedByServer) || (NetworkManager.DistributedAuthorityMode); -#else - // Return false for all client owners but return true for the server - return NetworkObject.IsOwnedByServer; -#endif } return true; } @@ -1455,7 +1446,6 @@ protected override void OnSynchronize(ref BufferSerializer serializer) if (serializer.IsWriter) { -#if NGO_DAMODE // DANGO-TODO: This magic value is sent to the server in order to identify the network transform. // The server discards it before forwarding synchronization data to other clients. if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) @@ -1463,7 +1453,6 @@ protected override void OnSynchronize(ref BufferSerializer serializer) var writer = serializer.GetFastBufferWriter(); writer.WriteValueSafe(k_NetworkTransformStateMagic); } -#endif synchronizationState.IsTeleportingNextFrame = true; var transformToCommit = transform; @@ -2118,13 +2107,8 @@ private void ApplyAuthoritativeState() // The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated // at the end of this method and assure that when not interpolating the non-authoritative side // cannot make adjustments to any portions the transform not being synchronized. -#if NGO_DAMODE var adjustedPosition = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : m_CurrentPosition; var adjustedRotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : m_CurrentRotation; -#else - var adjustedPosition = m_CurrentPosition; - var adjustedRotation = m_CurrentRotation; -#endif var adjustedRotAngles = adjustedRotation.eulerAngles; var adjustedScale = m_CurrentScale; @@ -2250,14 +2234,12 @@ private void ApplyAuthoritativeState() { m_CurrentPosition = adjustedPosition; } -#if NGO_DAMODE if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.MovePosition(m_CurrentPosition); transform.position = m_RigidbodyInternal.position; } else -#endif { if (InLocalSpace) { @@ -2279,14 +2261,12 @@ private void ApplyAuthoritativeState() m_CurrentRotation = adjustedRotation; } -#if NGO_DAMODE if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.MoveRotation(m_CurrentRotation); transform.rotation = m_RigidbodyInternal.rotation; } else -#endif { if (InLocalSpace) { @@ -2401,12 +2381,10 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.position = currentPosition; } -#if NGO_DAMODE if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.position = transform.position; } -#endif if (Interpolate) { @@ -2502,13 +2480,11 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.rotation = currentRotation; } -#if NGO_DAMODE if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.rotation = transform.rotation; m_RigidbodyInternal.MoveRotation(transform.rotation); } -#endif if (Interpolate) { @@ -2831,12 +2807,10 @@ public override void OnNetworkSpawn() Initialize(); -#if NGO_DAMODE if (CanCommitToTransform) { SetState(GetSpaceRelativePosition(), GetSpaceRelativeRotation(), GetScale(), false); } -#endif } private void CleanUpOnDestroyOrDespawn() @@ -2885,13 +2859,8 @@ protected virtual void OnInitialize(ref NetworkVariable r private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; -#if NGO_DAMODE var position = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : GetSpaceRelativePosition(); var rotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : GetSpaceRelativeRotation(); -#else - var position = GetSpaceRelativePosition(); - var rotation = GetSpaceRelativeRotation(); -#endif UpdatePositionInterpolator(position, serverTime, true); UpdatePositionSlerp(); @@ -2915,7 +2884,6 @@ private void InternalInitialization(bool isOwnershipChange = false) var currentPosition = GetSpaceRelativePosition(); var currentRotation = GetSpaceRelativeRotation(); -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { RegisterNetworkManagerForTickUpdate(NetworkManager); @@ -2933,7 +2901,6 @@ private void InternalInitialization(bool isOwnershipChange = false) m_RigidbodyInternal.position = currentPosition; m_RigidbodyInternal.rotation = currentRotation; } -#endif if (CanCommitToTransform) { @@ -3109,13 +3076,11 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s transform.SetPositionAndRotation(pos, rot); } -#if NGO_DAMODE if (m_UseRigidbodyForMotion && shouldTeleport) { m_RigidbodyInternal.position = transform.position; m_RigidbodyInternal.rotation = transform.rotation; } -#endif transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; @@ -3192,9 +3157,7 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #endregion #region UPDATES AND AUTHORITY CHECKS -#if NGO_DAMODE private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; -#endif private void UpdateInterpolation() { // Non-Authority @@ -3203,7 +3166,6 @@ private void UpdateInterpolation() var serverTime = m_CachedNetworkManager.ServerTime; var cachedServerTime = serverTime.Time; var offset = 0f; -#if NGO_DAMODE var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; // With owner authoritative mode, non-authority clients can lag behind // by more than 1 tick period of time. The current "solution" for now @@ -3214,13 +3176,6 @@ private void UpdateInterpolation() ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); offset = m_NetworkTransformTickRegistration.Offset; } -#else - // With owner authoritative mode, non-authority clients can lag behind - // by more than 1 tick period of time. The current "solution" for now - // is to make their cachedRenderTime run 2 ticks behind. - var ticksAgo = (!IsServerAuthoritative() && !IsServer) ? 2 : 1; - var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; -#endif var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo, offset).Time; @@ -3254,11 +3209,7 @@ private void UpdateInterpolation() protected virtual void Update() { // If not spawned or this instance has authority, exit early -#if NGO_DAMODE if (!IsSpawned || CanCommitToTransform || m_UseRigidbodyForMotion) -#else - if (!IsSpawned || CanCommitToTransform) -#endif { return; } @@ -3270,7 +3221,7 @@ protected virtual void Update() ApplyAuthoritativeState(); } -#if NGO_DAMODE + /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3294,9 +3245,6 @@ internal void OnFixedUpdate() // Apply the current authoritative state ApplyAuthoritativeState(); } -#endif - - /// /// Override this method and return false to switch to owner authoritative mode @@ -3304,12 +3252,10 @@ internal void OnFixedUpdate() /// ( or ) where when false it runs as owner-client authoritative protected virtual bool OnIsServerAuthoritative() { -#if NGO_DAMODE if (m_CachedNetworkManager) { return !m_CachedNetworkManager.DistributedAuthorityMode; } -#endif return true; } @@ -3370,11 +3316,9 @@ private void UpdateTransformState() NetworkObjectId = NetworkObjectId, NetworkBehaviourId = NetworkBehaviourId, State = m_LocalAuthoritativeNetworkState, -#if NGO_DAMODE DistributedAuthorityMode = m_CachedNetworkManager.DistributedAuthorityMode, // Don't populate if we are the DAHost as we send directly to each client TargetIds = m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.DAHost ? NetworkObject.Observers.ToArray() : null, -#endif }; @@ -3416,7 +3360,6 @@ private void UpdateTransformState() #region NETWORK TICK REGISTRATOIN AND HANDLING private static Dictionary s_NetworkTickRegistration = new Dictionary(); -#if NGO_DAMODE internal static float GetTickLatency(NetworkManager networkManager) { if (s_NetworkTickRegistration.ContainsKey(networkManager)) @@ -3451,7 +3394,6 @@ public static float GetTickLatencyInSeconds() { return GetTickLatencyInSeconds(NetworkManager.Singleton); } -#endif private static void RemoveTickUpdate(NetworkManager networkManager) { @@ -3482,12 +3424,10 @@ public void Remove() RemoveTickUpdate(m_NetworkManager); } -#if NGO_DAMODE internal float TicksAgoInSeconds() { return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; } -#endif /// /// Invoked once per network tick, this will update any registered @@ -3495,7 +3435,6 @@ internal float TicksAgoInSeconds() /// private void TickUpdate() { -#if NGO_DAMODE if (m_UnityTransport != null) { // Determine the desired ticks ago by the RTT (this really should be the combination of the @@ -3515,7 +3454,6 @@ private void TickUpdate() // the relative starting interpolation point for the next update Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); } -#endif // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before if (m_NetworkManager.ServerTime.Tick <= m_LastTick) @@ -3532,7 +3470,7 @@ private void TickUpdate() m_LastTick = m_NetworkManager.ServerTime.Tick; } -#if NGO_DAMODE + private UnityTransport m_UnityTransport; private float m_TickFrequency; private float m_OffsetTickFrequency; @@ -3544,14 +3482,12 @@ private void TickUpdate() private float m_PreviousTicksAgo; private List m_TicksAgoSamples = new List(); -#endif public NetworkTransformTickRegistration(NetworkManager networkManager) { m_NetworkManager = networkManager; m_NetworkTickUpdate = new Action(TickUpdate); networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate; -#if NGO_DAMODE m_TickRate = (int)m_NetworkManager.NetworkConfig.TickRate; m_TickFrequency = 1.0f / m_TickRate; // For the offset, it uses the fractional remainder of the tick to determine the offset. @@ -3567,7 +3503,6 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) } TicksAgo = 1f; m_PreviousTicksAgo = 1f; -#endif if (networkManager.IsServer) { networkManager.OnServerStopped += OnNetworkManagerStopped; @@ -3587,7 +3522,6 @@ internal void RegisterForTickSynchronization() m_NextTickSync = NetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); } -#if NGO_DAMODE private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkManager) { if (!s_NetworkTickRegistration.ContainsKey(networkManager)) @@ -3595,7 +3529,6 @@ private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkMa s_NetworkTickRegistration.Add(networkManager, new NetworkTransformTickRegistration(networkManager)); } } -#endif /// /// Will register the NetworkTransform instance for the single tick update entry point. @@ -3605,17 +3538,11 @@ private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkMa /// private static void RegisterForTickUpdate(NetworkTransform networkTransform) { -#if NGO_DAMODE + if (!networkTransform.NetworkManager.DistributedAuthorityMode && !s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) { s_NetworkTickRegistration.Add(networkTransform.NetworkManager, new NetworkTransformTickRegistration(networkTransform.NetworkManager)); } -#else - if (!s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) - { - s_NetworkTickRegistration.Add(networkTransform.NetworkManager, new NetworkTransformTickRegistration(networkTransform.NetworkManager)); - } -#endif networkTransform.RegisterForTickSynchronization(); s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Add(networkTransform); @@ -3635,19 +3562,11 @@ private static void DeregisterForTickUpdate(NetworkTransform networkTransform) if (s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) { s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Remove(networkTransform); -#if NGO_DAMODE if (!networkTransform.NetworkManager.DistributedAuthorityMode && s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Count == 0) { var registrationEntry = s_NetworkTickRegistration[networkTransform.NetworkManager]; registrationEntry.Remove(); } -#else - if (s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Count == 0) - { - var registrationEntry = s_NetworkTickRegistration[networkTransform.NetworkManager]; - registrationEntry.Remove(); - } -#endif } } #endregion diff --git a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs index 82563667ef..f14c638bcf 100644 --- a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE using System.Collections.Generic; using Unity.Collections; using Unity.Jobs; @@ -227,4 +226,3 @@ public void Execute(int index) } } } -#endif diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 5883f59967..011fe5d344 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -30,9 +30,7 @@ public class NetworkManagerEditor : UnityEditor.Editor private SerializedProperty m_ProtocolVersionProperty; private SerializedProperty m_NetworkTransportProperty; private SerializedProperty m_TickRateProperty; -#if NGO_DAMODE private SerializedProperty m_SessionModeProperty; -#endif private SerializedProperty m_ClientConnectionBufferTimeoutProperty; private SerializedProperty m_ConnectionApprovalProperty; private SerializedProperty m_EnsureNetworkVariableLengthSafetyProperty; @@ -99,9 +97,7 @@ private void Initialize() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); -#if NGO_DAMODE m_SessionModeProperty = m_NetworkConfigProperty.FindPropertyRelative("SessionMode"); -#endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); @@ -134,9 +130,7 @@ private void CheckNullProperties() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); -#if NGO_DAMODE m_SessionModeProperty = m_NetworkConfigProperty.FindPropertyRelative("SessionMode"); -#endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); @@ -170,9 +164,7 @@ public override void OnInspectorGUI() serializedObject.Update(); EditorGUILayout.PropertyField(m_RunInBackgroundProperty); EditorGUILayout.PropertyField(m_LogLevelProperty); -#if NGO_DAMODE EditorGUILayout.PropertyField(m_SessionModeProperty); -#endif EditorGUILayout.Space(); EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_ProtocolVersionProperty); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 177e662014..cc4b9d3982 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -50,12 +50,10 @@ public override void OnInspectorGUI() { var guiEnabled = GUI.enabled; GUI.enabled = false; -#if NGO_DAMODE if (m_NetworkObject.NetworkManager.DistributedAuthorityMode) { EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(NetworkObject.Ownership))); } -#endif EditorGUILayout.TextField(nameof(NetworkObject.GlobalObjectIdHash), m_NetworkObject.GlobalObjectIdHash.ToString()); EditorGUILayout.TextField(nameof(NetworkObject.NetworkObjectId), m_NetworkObject.NetworkObjectId.ToString()); EditorGUILayout.TextField(nameof(NetworkObject.OwnerClientId), m_NetworkObject.OwnerClientId.ToString()); @@ -146,7 +144,7 @@ private void OnDestroy() } } -#if NGO_DAMODE + [CustomPropertyDrawer(typeof(NetworkObject.OwnershipStatus))] public class NetworkObjectOwnership : PropertyDrawer { @@ -190,5 +188,4 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten EditorGUI.EndProperty(); } } -#endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 832f0916f8..ff806c6354 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -149,9 +149,6 @@ public class NetworkConfig /// public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets) - -#if NGO_DAMODE - [Tooltip("Determines if the network session will run in client-server or distributed authority mode.")] public SessionModeTypes SessionMode; @@ -160,7 +157,6 @@ public class NetworkConfig [Tooltip("When enabled (default), the player prefab will automatically be spawned (client-side) upon the client being approved and synchronized.")] public bool AutoSpawnPlayerPrefabClientSide = true; -#endif /// /// Returns a base64 encoded version of the configuration diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs index ba7be5e309..63b24bd035 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs @@ -4,13 +4,11 @@ namespace Unity.Netcode { -#if NGO_DAMODE public enum SessionModeTypes { ClientServer, DistributedAuthority } -#endif /// /// A NetworkClient @@ -43,7 +41,6 @@ public class NetworkClient /// internal bool IsApproved { get; set; } -#if NGO_DAMODE public SessionModeTypes SessionModeType { get; internal set; } public bool DAHost { get; internal set; } @@ -52,7 +49,6 @@ public class NetworkClient /// Is true when the client has been assigned session ownership in distributed authority mode /// public bool IsSessionOwner { get; internal set; } -#endif /// /// The ClientId of the NetworkClient @@ -81,7 +77,6 @@ internal bool SetRole(bool isServer, bool isClient, NetworkManager networkManage if (networkManager != null) { SpawnManager = networkManager.SpawnManager; -#if NGO_DAMODE SessionModeType = networkManager.NetworkConfig.SessionMode; if (SessionModeType == SessionModeTypes.DistributedAuthority) @@ -101,7 +96,6 @@ internal bool SetRole(bool isServer, bool isClient, NetworkManager networkManage return false; } } -#endif } return true; } @@ -120,9 +114,7 @@ private void ResetClient(bool isServer, bool isClient) IsConnected = false; IsApproved = false; SpawnManager = null; -#if NGO_DAMODE DAHost = false; -#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 2a64160522..cb19efe1d9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -7,9 +7,6 @@ using Unity.Collections.LowLevel.Unsafe; using Unity.Profiling; using UnityEngine; -#if !NGO_DAMODE -using Object = UnityEngine.Object; -#endif namespace Unity.Netcode { @@ -446,12 +443,8 @@ internal void ConnectEventHandler(ulong transportClientId) { if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { -#if NGO_DAMODE var serverOrService = NetworkManager.DistributedAuthorityMode ? NetworkManager.CMBServiceConnection ? "service" : "DAHost" : "server"; NetworkLog.LogInfo($"[Approval Pending][Client] Transport connection with {serverOrService} established! Awaiting connection approval..."); -#else - NetworkLog.LogInfo("[Pending Client] Transport connection with server established! Awaiting connection approval..."); -#endif } SendConnectionRequest(); @@ -558,11 +551,10 @@ private void SendConnectionRequest() { var message = new ConnectionRequestMessage { -#if NGO_DAMODE CMBServiceConnection = NetworkManager.CMBServiceConnection, TickRate = NetworkManager.NetworkConfig.TickRate, EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement, -#endif + // Since only a remote client will send a connection request, we should always force the rebuilding of the NetworkConfig hash value ConfigHash = NetworkManager.NetworkConfig.GetConfig(false), ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval, @@ -743,11 +735,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne RemovePendingClient(ownerClientId); var client = AddClient(ownerClientId); -#if NGO_DAMODE if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && NetworkManager.NetworkConfig.PlayerPrefab != null) -#else - if (response.CreatePlayerObject && NetworkManager.NetworkConfig.PlayerPrefab != null) -#endif { var prefabNetworkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash; @@ -762,9 +750,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne HasTransform = prefabNetworkObject.SynchronizeTransform, Hash = playerPrefabHash, TargetClientId = ownerClientId, -#if NGO_DAMODE DontDestroyWithOwner = prefabNetworkObject.DontDestroyWithOwner, -#endif Transform = new NetworkObject.SceneObject.TransformData { Position = response.Position.GetValueOrDefault(), @@ -794,9 +780,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { OwnerClientId = ownerClientId, NetworkTick = NetworkManager.LocalTime.Tick, -#if NGO_DAMODE IsDistributedAuthority = NetworkManager.DistributedAuthorityMode, -#endif ConnectedClientIds = new NativeArray(ConnectedClientIds.Count, Allocator.Temp) }; @@ -854,20 +838,16 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { InvokeOnPeerConnectedCallback(ownerClientId); } -#if NGO_DAMODE NetworkManager.SpawnManager.DistributeNetworkObjects(ownerClientId); -#endif } else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) { NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId); } else if (!NetworkManager.DistributedAuthorityMode) -#endif { NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId); } @@ -878,7 +858,6 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne LocalClient = client; NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId); LocalClient.IsConnected = true; -#if NGO_DAMODE // If running mock service, then set the instance as the default session owner if (NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) { @@ -890,7 +869,6 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { CreateAndSpawnPlayer(ownerClientId); } -#endif } if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkManager.NetworkConfig.PlayerPrefab == null)) @@ -898,13 +876,11 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne return; } -#if NGO_DAMODE // Players are always spawned by their respective client, exit early. (DAHost mode anyway, CMB Service will never spawn player prefab) if (NetworkManager.DistributedAuthorityMode) { return; } -#endif // Separating this into a contained function call for potential further future separation of when this notification is sent. ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash); } @@ -923,7 +899,6 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne } } -#if NGO_DAMODE /// /// Client-Side Spawning in distributed authority mode uses this to spawn the player. /// @@ -941,7 +916,6 @@ internal void CreateAndSpawnPlayer(ulong ownerId, Vector3 position = default, Qu } } } -#endif /// /// Spawns the newly approved player @@ -963,10 +937,9 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) var message = new CreateObjectMessage { ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key), -#if NGO_DAMODE IncludesSerializedObject = true, -#endif }; + message.ObjectInfo.Hash = playerPrefabHash; message.ObjectInfo.IsSceneObject = false; message.ObjectInfo.HasParent = false; @@ -1007,7 +980,6 @@ internal NetworkClient AddClient(ulong clientId) ConnectedClientsList.Add(networkClient); } -#if NGO_DAMODE if (NetworkManager.LocalClientId != clientId) { if ((!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer) || @@ -1038,17 +1010,10 @@ internal NetworkClient AddClient(ulong clientId) networkObject.Observers.Add(clientId); } } -#else - var message = new ClientConnectedMessage { ClientId = clientId }; - NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); - ConnectedClientIds.Add(clientId); -#endif return networkClient; } -#if NGO_DAMODE - /// /// Invoked on clients when another client disconnects /// @@ -1071,7 +1036,6 @@ internal void RemoveClient(ulong clientId) networkObject.Observers.Remove(clientId); } } -#endif /// /// DANGO-TODO: Until we have the CMB Server end-to-end with all features verified working via integration tests, @@ -1105,7 +1069,6 @@ internal void OnClientDisconnectFromServer(ulong clientId) { if (!playerObject.DontDestroyWithOwner) { -#if NGO_DAMODE // DANGO-TODO: This is something that would be best for CMB Service to handle as it is part of the disconnection process // If a player NetworkObject is being despawned, make sure to remove all children if they are marked to not be destroyed // with the owner. @@ -1125,17 +1088,14 @@ internal void OnClientDisconnectFromServer(ulong clientId) } } } -#endif if (NetworkManager.PrefabHandler.ContainsHandler(playerObject.GlobalObjectIdHash)) { -#if NGO_DAMODE if (NetworkManager.DAHost && NetworkManager.DistributedAuthorityMode) { NetworkManager.SpawnManager.DespawnObject(playerObject, true, NetworkManager.DistributedAuthorityMode); } else -#endif { NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(playerObject); } @@ -1144,11 +1104,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) { // 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. -#if NGO_DAMODE NetworkManager.SpawnManager.DespawnObject(playerObject, true, NetworkManager.DistributedAuthorityMode); -#else - NetworkManager.SpawnManager.DespawnObject(playerObject, true); -#endif } } else if (!NetworkManager.ShutdownInProgress) @@ -1174,11 +1130,9 @@ internal void OnClientDisconnectFromServer(ulong clientId) else { // Handle changing ownership and prefab handlers -#if NGO_DAMODE var clientCounter = 0; var predictedClientCount = ConnectedClientsList.Count - 1; var remainingClients = NetworkManager.DistributedAuthorityMode ? ConnectedClientsList.Where((c) => c.ClientId != clientId).ToList() : null; -#endif for (int i = clientOwnedObjects.Count - 1; i >= 0; i--) { var ownedObject = clientOwnedObjects[i]; @@ -1188,23 +1142,16 @@ internal void OnClientDisconnectFromServer(ulong clientId) { if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash)) { -#if NGO_DAMODE NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); -#endif NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]); } else { -#if NGO_DAMODE NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); -#else - Object.Destroy(ownedObject.gameObject); -#endif } } else if (!NetworkManager.ShutdownInProgress) { -#if NGO_DAMODE // NOTE: All of the below code only handles ownership transfer. // For client-server, we just remove the ownership. // For distributed authority, we need to change ownership based on parenting @@ -1258,7 +1205,6 @@ internal void OnClientDisconnectFromServer(ulong clientId) } } else -#endif { ownedObject.RemoveOwnership(); } @@ -1283,7 +1229,6 @@ internal void OnClientDisconnectFromServer(ulong clientId) var message = new ClientDisconnectedMessage { ClientId = clientId }; MessageManager?.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode && !NetworkManager.ShutdownInProgress && NetworkManager.IsListening) { var newSessionOwner = NetworkManager.LocalClientId; @@ -1314,7 +1259,6 @@ internal void OnClientDisconnectFromServer(ulong clientId) MessageManager?.SendMessage(ref sessionOwnerMessage, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); NetworkManager.SetSessionOwner(newSessionOwner); } -#endif } // If the client ID transport map exists diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 6da06eef41..e190702065 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -44,11 +44,7 @@ internal void NetworkBehaviourUpdate() for (int i = 0; i < m_ConnectionManager.ConnectedClientsList.Count; i++) { var client = m_ConnectionManager.ConnectedClientsList[i]; -#if NGO_DAMODE if (m_NetworkManager.DistributedAuthorityMode || dirtyObj.IsNetworkVisibleTo(client.ClientId)) -#else - if (dirtyObj.IsNetworkVisibleTo(client.ClientId)) -#endif { // Sync just the variables for just the objects this client sees for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++) @@ -98,10 +94,8 @@ internal void NetworkBehaviourUpdate() foreach (var dirtyobj in m_DirtyNetworkObjects) { dirtyobj.PostNetworkVariableWrite(); -#if NGO_DAMODE // Once done processing, we set the previous owner id to the current owner id dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId; -#endif } m_DirtyNetworkObjects.Clear(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 44d9788609..5e83ef2154 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using Unity.Collections; -#if NGO_DAMODE using System.Linq; -#endif using UnityEngine; #if UNITY_EDITOR using UnityEditor; @@ -37,7 +35,6 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem #pragma warning restore IDE1006 // restore naming rule violation check -#if NGO_DAMODE /// /// Distributed Authority Mode /// Returns true if the current session is running in distributed authority mode. @@ -139,10 +136,6 @@ internal void SetSessionOwner(ulong sessionOwner) LocalClient.IsSessionOwner = LocalClientId == sessionOwner; } - - -#if NGO_DAMODE - // TODO: Make this internal after testing public void PromoteSessionOwner(ulong clientId) { @@ -167,9 +160,6 @@ public void PromoteSessionOwner(ulong clientId) ConnectionManager.SendMessage(ref sessionOwnerMessage, NetworkDelivery.ReliableSequenced, targetClient); } } -#endif - -#endif public void NetworkUpdate(NetworkUpdateStage updateStage) { @@ -193,13 +183,11 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) break; case NetworkUpdateStage.PostLateUpdate: { -#if NGO_DAMODE // Handle deferred despawning if (DistributedAuthorityMode) { SpawnManager.DeferredDespawnUpdate(ServerTime); } -#endif // This should be invoked just prior to the MessageManager processes its outbound queue. SceneManager.CheckForAndSendNetworkObjectSceneChanged(); @@ -216,7 +204,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period. DeferredMessageManager.CleanupStaleTriggers(); -#if NGO_DAMODE // DANGO-TODO-MVP: Remove this once the service handles object distribution // NOTE: This needs to be the last thing done and should happen exactly at this point // in the update @@ -226,7 +213,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) SpawnManager.DistributeNetworkObjects(ClientToRedistribute); ClientToRedistribute = 0; } -#endif if (m_ShuttingDown) { @@ -330,26 +316,12 @@ public ulong LocalClientId /// /// Gets a dictionary of connected clients and their clientId keys. /// -#if NGO_DAMODE public IReadOnlyDictionary ConnectedClients => ConnectionManager.ConnectedClients; -#else - /// - /// Gets a dictionary of connected clients and their clientId keys. This is only accessible on the server. - /// - public IReadOnlyDictionary ConnectedClients => IsServer ? ConnectionManager.ConnectedClients : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClients)} should only be accessed on server."); -#endif /// /// Gets a list of connected clients. /// -#if NGO_DAMODE public IReadOnlyList ConnectedClientsList => ConnectionManager.ConnectedClientsList; -#else - /// - /// Gets a list of connected clients. This is only accessible on the server. - /// - public IReadOnlyList ConnectedClientsList => IsServer ? ConnectionManager.ConnectedClientsList : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientsList)} should only be accessed on server."); -#endif /// /// Gets a list of just the IDs of all connected clients. @@ -951,10 +923,9 @@ public int MaximumFragmentedMessageSize internal void Initialize(bool server) { -#if NGO_DAMODE - // TODO: Remove this before finalizing the experimental release + + //DANGOEXP TODO: Remove this before finalizing the experimental release NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode; -#endif // Make sure the ServerShutdownState is reset when initializing if (server) @@ -1258,7 +1229,6 @@ private void HostServerInitialize() } else { -#if NGO_DAMODE var response = new ConnectionApprovalResponse { Approved = true, @@ -1266,14 +1236,6 @@ private void HostServerInitialize() CreatePlayerObject = DistributedAuthorityMode || NetworkConfig.PlayerPrefab != null, }; ConnectionManager.HandleConnectionApproval(ServerClientId, response); -#else - var response = new ConnectionApprovalResponse - { - Approved = true, - CreatePlayerObject = NetworkConfig.PlayerPrefab != null - }; - ConnectionManager.HandleConnectionApproval(ServerClientId, response); -#endif } SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 3d83e90961..5d4656e162 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -258,7 +258,6 @@ private GlobalObjectId GetGlobalId() /// public NetworkManager NetworkManager => NetworkManagerOwner ? NetworkManagerOwner : NetworkManager.Singleton; -#if NGO_DAMODE /// /// Useful to know if we should or should not send a message /// @@ -881,7 +880,6 @@ private bool InternalHasAuthority() var networkManager = NetworkManager; return networkManager.DistributedAuthorityMode ? OwnerClientId == networkManager.LocalClientId : networkManager.IsServer; } -#endif /// /// The NetworkManager that owns this NetworkObject. @@ -901,9 +899,7 @@ private bool InternalHasAuthority() /// public ulong OwnerClientId { get; internal set; } -#if NGO_DAMODE internal ulong PreviousOwnerId; -#endif /// /// If true, the object will always be replicated as root on clients and the parent will be ignored. @@ -952,12 +948,11 @@ private bool InternalHasAuthority() /// public bool? IsSceneObject { get; internal set; } -#if NGO_DAMODE + //DANGOEXP TODO: Determine if we want to keep this public void SetSceneObjectStatus(bool isSceneObject = false) { IsSceneObject = isSceneObject; } -#endif /// /// Gets whether or not the object should be automatically removed when the scene is unloaded. @@ -1175,7 +1170,6 @@ public void NetworkShow(ulong clientId) throw new SpawnStateException("Object is not spawned"); } -#if NGO_DAMODE if (!HasAuthority) { if (NetworkManager.DistributedAuthorityMode) @@ -1200,18 +1194,6 @@ public void NetworkShow(ulong clientId) throw new NotServerException("Only server can change visibility"); } } -#else - if (!NetworkManager.IsServer) - { - throw new NotServerException("Only server can change visibility"); - } - - if (Observers.Contains(clientId)) - { - throw new VisibilityChangeException($"The object {name} is already visible to Client-{clientId}!"); - } -#endif - if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) { @@ -1249,18 +1231,9 @@ public static void NetworkShow(List networkObjects, ulong clientI return; } -#if !NGO_DAMODE - var networkManager = networkObjects[0].NetworkManager; - if (!networkManager.IsServer) - { - throw new NotServerException("Only server can change visibility"); - } -#endif - // Do the safety loop first to prevent putting the netcode in an invalid state. for (int i = 0; i < networkObjects.Count; i++) { -#if NGO_DAMODE var networkObject = networkObjects[i]; var networkManager = networkObject.NetworkManager; @@ -1289,7 +1262,7 @@ public static void NetworkShow(List networkObjects, ulong clientI throw new NotServerException("Only server can change visibility"); } } -#endif + if (!networkObjects[i].IsSpawned) { throw new SpawnStateException("Object is not spawned"); @@ -1333,7 +1306,6 @@ public void NetworkHide(ulong clientId) throw new SpawnStateException("Object is not spawned"); } -#if NGO_DAMODE if (!HasAuthority && !NetworkManager.DAHost) { if (NetworkManager.DistributedAuthorityMode) @@ -1345,31 +1317,16 @@ public void NetworkHide(ulong clientId) throw new NotServerException("Only the authority can change visibility"); } } -#else - if (!NetworkManager.IsServer) - { - throw new NotServerException("Only server can change visibility"); - } - - if (clientId == NetworkManager.ServerClientId) - { - throw new VisibilityChangeException("Cannot hide an object from the server"); - } -#endif if (!NetworkManager.SpawnManager.RemoveObjectFromShowingTo(this, clientId)) { if (!Observers.Contains(clientId)) { -#if NGO_DAMODE if (NetworkManager.LogLevel <= LogLevel.Developer) { Debug.LogWarning($"{name} is already hidden from Client-{clientId}! (ignoring)"); return; } -#else - throw new VisibilityChangeException("The object is already hidden"); -#endif } Observers.Remove(clientId); @@ -1377,16 +1334,13 @@ public void NetworkHide(ulong clientId) { NetworkObjectId = NetworkObjectId, DestroyGameObject = !IsSceneObject.Value, -#if NGO_DAMODE IsDistributedAuthority = NetworkManager.DistributedAuthorityMode, IsTargetedDestroy = NetworkManager.DistributedAuthorityMode, TargetClientId = clientId, // Just always populate this value whether we write it or not DeferredDespawnTick = DeferredDespawnTick, -#endif }; var size = 0; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { if (!NetworkManager.DAHost) @@ -1410,7 +1364,6 @@ public void NetworkHide(ulong clientId) } } else -#endif { // Send destroy call size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); @@ -1436,37 +1389,15 @@ public void NetworkHide(ulong clientId) /// The targeted client public static void NetworkHide(List networkObjects, ulong clientId) { - -#if NGO_DAMODE if (networkObjects == null || networkObjects.Count == 0) { NetworkLog.LogErrorServer($"At least one {nameof(NetworkObject)} has to be provided when hiding a list of {nameof(NetworkObject)}s!"); return; } -#else - if (networkObjects == null || networkObjects.Count == 0) - { - throw new ArgumentNullException("At least one " + nameof(NetworkObject) + " has to be provided"); - } - - var networkManager = networkObjects[0].NetworkManager; - - if (!networkManager.IsServer) - { - throw new NotServerException("Only server can change visibility"); - } - - if (clientId == NetworkManager.ServerClientId) - { - throw new VisibilityChangeException("Cannot hide an object from the server"); - } -#endif // Do the safety loop first to prevent putting the netcode in an invalid state. for (int i = 0; i < networkObjects.Count; i++) { - -#if NGO_DAMODE var networkObject = networkObjects[i]; var networkManager = networkObject.NetworkManager; @@ -1497,7 +1428,6 @@ public static void NetworkHide(List networkObjects, ulong clientI } // CLIENT SPAWNING TODO: Log error and continue as opposed to throwing an exception -#endif if (!networkObjects[i].IsSpawned) { throw new SpawnStateException("Object is not spawned"); @@ -1528,12 +1458,8 @@ private void OnDestroy() return; } -#if NGO_DAMODE // Authority is the server (client-server) and the owner or DAHost (distributed authority) when destroying a NetworkObject var isAuthority = HasAuthority || NetworkManager.DAHost; -#else - var isAuthority = NetworkManager.IsServer; -#endif if (NetworkManager.IsListening && !isAuthority && IsSpawned && (IsSceneObject == null || (IsSceneObject.Value != true))) @@ -1543,7 +1469,6 @@ private void OnDestroy() if (!NetworkManager.ShutdownInProgress) { // Since we still have a session connection, log locally and on the server to inform user of this issue. -#if NGO_DAMODE if (NetworkManager.LogLevel <= LogLevel.Error) { if (NetworkManager.DistributedAuthorityMode) @@ -1555,9 +1480,6 @@ private void OnDestroy() NetworkLog.LogErrorServer($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); } } -#else - NetworkLog.LogErrorServer($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); -#endif return; } // Otherwise, clients can despawn NetworkObjects while shutting down and should not generate any messages when this happens @@ -1584,7 +1506,6 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla throw new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before spawning objects"); } -#if NGO_DAMODE if ((!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) || (NetworkManager.DistributedAuthorityMode && !NetworkManager.LocalClient.IsSessionOwner && NetworkManager.LocalClientId != ownerClientId)) { if (NetworkManager.DistributedAuthorityMode) @@ -1613,16 +1534,9 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } } -#else - if (!NetworkManager.IsServer) - { - throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s"); - } -#endif NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); -#if NGO_DAMODE if ((NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) || (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer)) { for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) @@ -1645,16 +1559,6 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla { NetworkLog.LogWarningServer($"Ran into unknown conditional check during spawn when determining distributed authority mode or not"); } -#else - for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) - { - if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId)) - { - NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ConnectedClientsList[i].ClientId, this); - } - } -#endif - } /// @@ -1707,13 +1611,9 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow return null; } -#if NGO_DAMODE ownerClientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; // We only need to check for authority when running in client-server mode if (!networkManager.IsServer && !networkManager.DistributedAuthorityMode) -#else - if (!networkManager.IsServer) -#endif { Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NotAuthority]); return null; @@ -1741,12 +1641,8 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow /// Should the object be destroyed when the scene is changed public void Spawn(bool destroyWithScene = false) { -#if NGO_DAMODE var clientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; SpawnInternal(destroyWithScene, clientId, false); -#else - SpawnInternal(destroyWithScene, NetworkManager.ServerClientId, false); -#endif } /// @@ -1793,16 +1689,11 @@ public void RemoveOwnership() /// The new owner clientId public void ChangeOwnership(ulong newOwnerClientId) { -#if NGO_DAMODE NetworkManager.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); -#else - NetworkManager.SpawnManager.ChangeOwnership(this, newOwnerClientId); -#endif } internal void InvokeBehaviourOnLostOwnership() { -#if NGO_DAMODE // Always update the ownership table in distributed authority mode if (NetworkManager.DistributedAuthorityMode) { @@ -1813,12 +1704,6 @@ internal void InvokeBehaviourOnLostOwnership() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); } -#else - if (!NetworkManager.IsServer) - { - NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); - } -#endif for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { ChildNetworkBehaviours[i].InternalOnLostOwnership(); @@ -1827,8 +1712,6 @@ internal void InvokeBehaviourOnLostOwnership() internal void InvokeBehaviourOnGainedOwnership() { - -#if NGO_DAMODE // Always update the ownership table in distributed authority mode if (NetworkManager.DistributedAuthorityMode) { @@ -1839,12 +1722,6 @@ internal void InvokeBehaviourOnGainedOwnership() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); } -#else - if (!NetworkManager.IsServer && NetworkManager.LocalClientId == OwnerClientId) - { - NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); - } -#endif for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { @@ -1886,13 +1763,11 @@ internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNe private Transform m_CachedParent; // What is our last set parent Transform reference? private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent -#if NGO_DAMODE /// /// With distributed authority, we need to have a way to determine if the parenting action is authorized. /// This is set when handling an incoming ParentSyncMessage and when running as a DAHost and a client has disconnected. /// internal bool AuthorityAppliedParenting = false; -#endif /// @@ -1913,9 +1788,7 @@ public bool WorldPositionStays() internal void SetCachedParent(Transform parentTransform) { -#if NGO_DAMODE AuthorityAppliedParenting = false; -#endif m_CachedParent = parentTransform; } @@ -2011,13 +1884,10 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) return false; } -#if NGO_DAMODE // DANGO-TODO: Do we want to worry about ownership permissions here? // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. var isAuthority = HasAuthority; -#else - var isAuthority = NetworkManager.IsServer; -#endif + // If we don't have authority and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. if (!isAuthority && !NetworkManager.ShutdownInProgress) @@ -2072,18 +1942,15 @@ private void OnTransformParentChanged() return; } var isAuthority = false; -#if NGO_DAMODE // With distributed authority, we need to track "valid authoritative" parenting changes. // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". isAuthority = HasAuthority || AuthorityAppliedParenting; var distributedAuthority = NetworkManager.DistributedAuthorityMode; -#else - isAuthority = NetworkManager.IsServer; -#endif + // If we do not have authority and we are spawned if (!isAuthority && IsSpawned) { -#if NGO_DAMODE + // If the cached parent has not already been set and we are in distributed authority mode, then log an exception and exit early as a non-authority instance // is trying to set the parent. if (distributedAuthority) @@ -2097,17 +1964,11 @@ private void OnTransformParentChanged() Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); } return; -#else - Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); - return; -#endif } if (!IsSpawned) { -#if NGO_DAMODE AuthorityAppliedParenting = false; -#endif // and we are removing the parent, then go ahead and allow parenting to occur if (transform.parent == null) { @@ -2129,18 +1990,14 @@ private void OnTransformParentChanged() if (!transform.parent.TryGetComponent(out var parentObject)) { transform.parent = m_CachedParent; -#if NGO_DAMODE AuthorityAppliedParenting = false; -#endif Debug.LogException(new InvalidParentException($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent")); return; } else if (!parentObject.IsSpawned) { transform.parent = m_CachedParent; -#if NGO_DAMODE AuthorityAppliedParenting = false; -#endif Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented under another spawned {nameof(NetworkObject)}")); return; } @@ -2153,10 +2010,8 @@ private void OnTransformParentChanged() removeParent = m_CachedParent != null; } -#if NGO_DAMODE // This can be reset within ApplyNetworkParenting var authorityApplied = AuthorityAppliedParenting; -#endif ApplyNetworkParenting(removeParent); var message = new ParentSyncMessage @@ -2165,9 +2020,7 @@ private void OnTransformParentChanged() IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue, LatestParent = m_LatestParent, RemoveParent = removeParent, -#if NGO_DAMODE AuthorityApplied = authorityApplied, -#endif WorldPositionStays = m_CachedWorldPositionStays, Position = m_CachedWorldPositionStays ? transform.position : transform.localPosition, Rotation = m_CachedWorldPositionStays ? transform.rotation : transform.localRotation, @@ -2182,7 +2035,6 @@ private void OnTransformParentChanged() m_CachedWorldPositionStays = true; } -#if NGO_DAMODE // If we are connected to a CMB service or we are running a mock CMB service then send to the "server" identifier if (distributedAuthority) { @@ -2226,24 +2078,6 @@ private void OnTransformParentChanged() NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx); } } -#else - // Otherwise we are running in client-server =or= this has to be a DAHost instance. - // Send to all connected clients. - unsafe - { - var maxCount = NetworkManager.ConnectedClientsIds.Count; - ulong* clientIds = stackalloc ulong[maxCount]; - int idx = 0; - foreach (var clientId in NetworkManager.ConnectedClientsIds) - { - if (Observers.Contains(clientId)) - { - clientIds[idx++] = clientId; - } - } - NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx); - } -#endif } // We're keeping this set called OrphanChildren which contains NetworkObjects @@ -2435,7 +2269,6 @@ internal List ChildNetworkBehaviours internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { writer.WriteValueSafe((ushort)ChildNetworkBehaviours.Count); @@ -2444,7 +2277,6 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie return; } } -#endif for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { var behavior = ChildNetworkBehaviours[i]; @@ -2489,7 +2321,6 @@ internal static void VerifyParentingStatus() /// internal bool SetNetworkVariableData(FastBufferReader reader, ulong clientId) { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { var readerPosition = reader.Position; @@ -2500,7 +2331,6 @@ internal bool SetNetworkVariableData(FastBufferReader reader, ulong clientId) return false; } } -#endif for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { @@ -2570,10 +2400,7 @@ internal struct SceneObject public uint Hash; public ulong NetworkObjectId; public ulong OwnerClientId; - -#if NGO_DAMODE public ushort OwnershipFlags; -#endif public bool IsPlayerObject { @@ -2620,7 +2447,6 @@ public bool DestroyWithScene set => ByteUtility.SetBit(ref m_BitField, 6, value); } -#if NGO_DAMODE public bool DontDestroyWithOwner { get => ByteUtility.GetBit(m_BitField, 7); @@ -2649,8 +2475,6 @@ public bool SpawnWithObservers // this will be populated with the known observers. public ulong[] Observers; -#endif - //If(Metadata.HasParent) public ulong ParentObjectId; @@ -2677,13 +2501,11 @@ public struct TransformData : INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { -#if NGO_DAMODE if (OwnerObject.NetworkManager.DistributedAuthorityMode) { HasOwnershipFlags = true; SpawnWithObservers = OwnerObject.SpawnWithObservers; } -#endif writer.WriteValueSafe(m_BitField); writer.WriteValueSafe(Hash); BytePacker.WriteValueBitPacked(writer, NetworkObjectId); @@ -2698,7 +2520,6 @@ public void Serialize(FastBufferWriter writer) } } -#if NGO_DAMODE if (HasOwnershipFlags) { writer.WriteValueSafe(OwnershipFlags); @@ -2712,7 +2533,6 @@ public void Serialize(FastBufferWriter writer) BytePacker.WriteValuePacked(writer, observer); } } -#endif var writeSize = 0; writeSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; @@ -2730,7 +2550,6 @@ public void Serialize(FastBufferWriter writer) // The NetworkSceneHandle is the server-side relative // scene handle that the NetworkObject resides in. -#if NGO_DAMODE if (OwnerObject.NetworkManager.DistributedAuthorityMode) { writer.WriteValue(OwnerObject.NetworkSceneHandle); @@ -2739,9 +2558,6 @@ public void Serialize(FastBufferWriter writer) { writer.WriteValue(OwnerObject.GetSceneOriginHandle()); } -#else - writer.WriteValue(OwnerObject.GetSceneOriginHandle()); -#endif // Synchronize NetworkVariables and NetworkBehaviours var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); @@ -2765,7 +2581,6 @@ public void Deserialize(FastBufferReader reader) } } -#if NGO_DAMODE if (HasOwnershipFlags) { reader.ReadValueSafe(out OwnershipFlags); @@ -2783,7 +2598,6 @@ public void Deserialize(FastBufferReader reader) Observers[i] = observerId; } } -#endif var readSize = 0; readSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; @@ -2904,11 +2718,7 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer } } -#if NGO_DAMODE internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) -#else - internal SceneObject GetMessageSceneObject(ulong targetClientId) -#endif { var obj = new SceneObject { @@ -2917,14 +2727,12 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) IsPlayerObject = IsPlayerObject, IsSceneObject = IsSceneObject ?? true, DestroyWithScene = DestroyWithScene, -#if NGO_DAMODE DontDestroyWithOwner = DontDestroyWithOwner, HasOwnershipFlags = NetworkManager.DistributedAuthorityMode, OwnershipFlags = (ushort)Ownership, SyncObservers = syncObservers, Observers = syncObservers ? Observers.ToArray() : null, NetworkSceneHandle = NetworkSceneHandle, -#endif Hash = HostCheckForGlobalObjectIdHashOverride(), OwnerObject = this, TargetClientId = targetClientId @@ -3009,14 +2817,9 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId) /// Deserialized scene object data /// FastBufferReader for the NetworkVariable data /// NetworkManager instance -#if NGO_DAMODE /// will be true if invoked by CreateObjectMessage /// The deserialized NetworkObject or null if deserialization failed internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false) -#else - /// The deserialized NetworkObject or null if deserialization failed - internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager) -#endif { //Attempt to create a local NetworkObject var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject); @@ -3055,7 +2858,6 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf // Spawn the NetworkObject networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene); -#if NGO_DAMODE if (sceneObject.SyncObservers) { foreach (var observer in sceneObject.Observers) @@ -3105,7 +2907,6 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf } } } -#endif return networkObject; } @@ -3165,16 +2966,12 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) return; } -#if NGO_DAMODE if (NetworkManager.SceneManager.IsSceneEventInProgress()) { return; } var isAuthority = HasAuthority; -#else - var isAuthority = NetworkManager.IsServer; -#endif SceneOriginHandle = scene.handle; // non-authority needs to update the NetworkSceneHandle @@ -3186,13 +2983,11 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) { // Since the authority is the source of truth for the NetworkSceneHandle, // the NetworkSceneHandle is the same as the SceneOriginHandle. -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } else -#endif { NetworkSceneHandle = SceneOriginHandle; } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 11fe3df957..cd29c6a651 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -39,13 +39,11 @@ public static class NetworkLog /// The message to log public static void LogInfoServer(string message) => LogServer(message, LogType.Info); -#if NGO_DAMODE /// /// Logs an info log locally and on the session owner if possible. /// /// The message to log public static void LogInfoSessionOwner(string message) => LogServer(message, LogType.Info); -#endif /// /// Logs a warning log locally and on the server if possible. @@ -66,12 +64,8 @@ private static void LogServer(string message, LogType logType) var networkManager = NetworkManagerOverride ??= NetworkManager.Singleton; // Get the sender of the local log ulong localId = networkManager?.LocalClientId ?? 0; -#if NGO_DAMODE bool isServer = networkManager && networkManager.DistributedAuthorityMode ? networkManager.LocalClient.IsSessionOwner : networkManager && !networkManager.DistributedAuthorityMode ? networkManager.IsServer : true; -#else - bool isServer = networkManager?.IsServer ?? true; -#endif switch (logType) { case LogType.Info: @@ -112,9 +106,7 @@ private static void LogServer(string message, LogType logType) { LogType = logType, Message = message, -#if NGO_DAMODE SenderId = localId -#endif }; var size = networkManager.ConnectionManager.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId); @@ -122,7 +114,6 @@ private static void LogServer(string message, LogType logType) } } -#if NGO_DAMODE private static string Header() { var networkManager = NetworkManagerOverride ??= NetworkManager.Singleton; @@ -137,14 +128,6 @@ private static string Header() internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[{Header()} Sender={sender}] {message}"); internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[{Header()} Sender={sender}] {message}"); -#else - internal static void LogInfoServerLocal(string message, ulong sender) => Debug.Log($"[Netcode-Server Sender={sender}] {message}"); - internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[Netcode-Server Sender={sender}] {message}"); - internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[Netcode-Server Sender={sender}] {message}"); -#endif - - - internal enum LogType : byte { Info, diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs index 71b43fdd0d..f05d000fec 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs @@ -15,9 +15,7 @@ protected struct TriggerData } protected struct TriggerInfo { -#if NGO_DAMODE public string MessageType; -#endif public float Expiry; public NativeList TriggerData; } @@ -39,11 +37,7 @@ internal DeferredMessageManager(NetworkManager networkManager) /// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed /// without the requested object ID being spawned, the triggers for it are automatically deleted. /// -#if NGO_DAMODE public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType) -#else - public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context) -#endif { if (!m_Triggers.TryGetValue(trigger, out var triggers)) { @@ -55,9 +49,7 @@ public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerTy { triggerInfo = new TriggerInfo { -#if NGO_DAMODE MessageType = messageType, -#endif Expiry = m_NetworkManager.RealTimeProvider.RealTimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout, TriggerData = new NativeList(Allocator.Persistent) }; @@ -100,7 +92,6 @@ public virtual unsafe void CleanupStaleTriggers() } } -#if NGO_DAMODE /// /// Used for testing purposes /// @@ -117,21 +108,11 @@ private string GetWarningMessage(IDeferredNetworkMessageManager.TriggerType trig return $"Deferred messages were received for a trigger of type {triggerType} associated with id ({key}), but the {nameof(NetworkObject)} was not received within the timeout period {spawnTimeout} second(s)."; } } -#else - private string GetWarningMessage(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo, float spawnTimeout) - { - return $"Deferred messages were received for a trigger of type {triggerType} associated with id ({key}), but the {nameof(NetworkObject)} was not received within the timeout period {spawnTimeout} second(s)."; - } -#endif protected virtual void PurgeTrigger(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo) { -#if NGO_DAMODE var logLevel = m_NetworkManager.DistributedAuthorityMode ? LogLevel.Developer : LogLevel.Normal; if (NetworkLog.CurrentLogLevel <= logLevel) -#else - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) -#endif { NetworkLog.LogWarning(GetWarningMessage(triggerType, key, triggerInfo, m_NetworkManager.NetworkConfig.SpawnTimeout)); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs index 6bdd11bc58..0f55de95c2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs @@ -18,11 +18,7 @@ internal enum TriggerType /// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed /// without the requested object ID being spawned, the triggers for it are automatically deleted. /// -#if NGO_DAMODE void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType = null); -#else - void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context); -#endif /// /// Cleans up any trigger that's existed for more than a second. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 225a20cb76..7ab41b8581 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -7,9 +7,8 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem public ulong NetworkObjectId; public ulong OwnerClientId; - -#if NGO_DAMODE - // DA-NGO CMB SERVICE NOTES: + // DANGOEXP TODO: Remove these notes or change their format + // SERVICE NOTES: // When forwarding the message to clients on the CMB Service side, // you can set the ClientIdCount to 0 and skip writing the ClientIds. // See the NetworkObjet.OwnershipRequest for more potential service side additions @@ -109,13 +108,11 @@ private void SetFlag(bool set, byte flag) if (set) { m_OwnershipMessageTypeFlags = (byte)(m_OwnershipMessageTypeFlags | flag); } else { m_OwnershipMessageTypeFlags = (byte)(m_OwnershipMessageTypeFlags & ~flag); } } -#endif public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, OwnerClientId); -#if NGO_DAMODE if (DistributedAuthorityMode) { BytePacker.WriteValueBitPacked(writer, ClientIdCount); @@ -150,7 +147,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } } } -#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) @@ -163,7 +159,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { ByteUnpacker.ReadValueBitPacked(reader, out ClientIdCount); @@ -205,11 +200,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int if (!networkManager.DAHost && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); -#else - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) - { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); -#endif return false; } return true; @@ -219,7 +209,6 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; -#if NGO_DAMODE // If we are the DAHost then forward this message if (networkManager.DAHost) { @@ -298,38 +287,8 @@ public void Handle(ref NetworkContext context) // Otherwise, we handle and extended ownership update HandleExtendedOwnershipUpdate(ref context); } -#else - var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; - var originalOwner = networkObject.OwnerClientId; - networkObject.OwnerClientId = OwnerClientId; - // We are current owner. - if (originalOwner == networkManager.LocalClientId) - { - networkObject.InvokeBehaviourOnLostOwnership(); - } - - // We are new owner. - if (OwnerClientId == networkManager.LocalClientId) - { - networkObject.InvokeBehaviourOnGainedOwnership(); - } - - // For all other clients that are neither the former or current owner, update the behaviours' properties - if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId) - { - for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) - { - networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); - } - } - - networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId); - networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); -#endif } -#if NGO_DAMODE - /// /// Handle the /// @@ -436,7 +395,5 @@ private void HandleOwnershipChange(ref NetworkContext context) networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); } -#endif - } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index aa5f66835c..819b632fa5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -6,17 +6,13 @@ internal struct ClientConnectedMessage : INetworkMessage, INetworkSerializeByMem public ulong ClientId; -#if NGO_DAMODE public bool ShouldSynchronize; -#endif public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, ClientId); -#if NGO_DAMODE writer.WriteValueSafe(ShouldSynchronize); -#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) @@ -27,9 +23,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return false; } ByteUnpacker.ReadValueBitPacked(reader, out ClientId); -#if NGO_DAMODE reader.ReadValueSafe(out ShouldSynchronize); -#endif return true; } @@ -37,7 +31,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; -#if NGO_DAMODE if ((ShouldSynchronize || networkManager.CMBServiceConnection) && networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) { networkManager.SceneManager.SynchronizeNetworkObjects(ClientId); @@ -47,7 +40,6 @@ public void Handle(ref NetworkContext context) // All modes support adding NetworkClients networkManager.ConnectionManager.AddClient(ClientId); } -#endif if (!networkManager.ConnectionManager.ConnectedClientIds.Contains(ClientId)) { networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId); @@ -57,7 +49,6 @@ public void Handle(ref NetworkContext context) networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId); } -#if NGO_DAMODE // DANGO-TODO: Remove the session owner object distribution check once the service handles object distribution if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && !networkManager.NetworkConfig.EnableSceneManagement) { @@ -70,7 +61,6 @@ public void Handle(ref NetworkContext context) networkManager.TickToRedistribute = networkManager.ServerTime.Tick + 20; } } -#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs index 1a863ce9e4..6cf0e413f0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs @@ -25,10 +25,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; -#if NGO_DAMODE // All modes support removing NetworkClients networkManager.ConnectionManager.RemoveClient(ClientId); -#endif networkManager.ConnectionManager.ConnectedClientIds.Remove(ClientId); if (networkManager.IsConnectedClient) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index c2295f2a45..174eba3ee1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -10,13 +10,11 @@ internal struct ConnectionApprovedMessage : INetworkMessage public ulong OwnerClientId; public int NetworkTick; -#if NGO_DAMODE // The cloud state service should set this if we are restoring a session public bool IsRestoredSession; public ulong CurrentSessionOwner; // Not serialized public bool IsDistributedAuthority; -#endif // Not serialized, held as references to serialize NetworkVariable data public HashSet SpawnedObjectsList; @@ -45,13 +43,11 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, OwnerClientId); BytePacker.WriteValueBitPacked(writer, NetworkTick); -#if NGO_DAMODE if (IsDistributedAuthority) { writer.WriteValueSafe(IsRestoredSession); BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner); } -#endif if (targetVersion >= k_VersionAddClientIds) { @@ -73,12 +69,8 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))) { sobj.Observers.Add(OwnerClientId); -#if NGO_DAMODE // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. var sceneObject = sobj.GetMessageSceneObject(OwnerClientId, IsDistributedAuthority); -#else - var sceneObject = sobj.GetMessageSceneObject(OwnerClientId); -#endif sceneObject.Serialize(writer); ++sceneObjectCount; } @@ -133,13 +125,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick); -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { reader.ReadValueSafe(out IsRestoredSession); ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner); } -#endif if (receivedMessageVersion >= k_VersionAddClientIds) { @@ -164,7 +154,7 @@ public void Handle(ref NetworkContext context) networkManager.LocalClientId = OwnerClientId; networkManager.MessageManager.SetLocalClientId(networkManager.LocalClientId); networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId); -#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) { networkManager.SetSessionOwner(CurrentSessionOwner); @@ -173,7 +163,6 @@ public void Handle(ref NetworkContext context) networkManager.SceneManager.InitializeScenesLoaded(); } } -#endif var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick); networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport. @@ -197,7 +186,6 @@ public void Handle(ref NetworkContext context) // Only if scene management is disabled do we handle NetworkObject synchronization at this point if (!networkManager.NetworkConfig.EnableSceneManagement) { -#if NGO_DAMODE // DANGO-TODO: This is a temporary fix for no DA CMB scene event handling. // We will either use this same concept or provide some way for the CMB state plugin to handle it. if (networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) @@ -208,9 +196,6 @@ public void Handle(ref NetworkContext context) { networkManager.SpawnManager.DestroySceneObjects(); } -#else - networkManager.SpawnManager.DestroySceneObjects(); -#endif m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount); @@ -226,12 +211,11 @@ public void Handle(ref NetworkContext context) // Mark the client being connected networkManager.IsConnectedClient = true; -#if NGO_DAMODE if (networkManager.AutoSpawnPlayerPrefabClientSide) { networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId); } -#endif + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { NetworkLog.LogInfo($"[Client-{OwnerClientId}][Scene Management Disabled] Synchronization complete!"); @@ -241,7 +225,6 @@ public void Handle(ref NetworkContext context) } else { -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement) { // Mark the client being connected @@ -258,11 +241,7 @@ public void Handle(ref NetworkContext context) // Synchronize the service with the initial session owner's loaded scenes and spawned objects networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); } -#endif } - - - ConnectedClientIds.Dispose(); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index a7457daa32..790791dd24 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -8,11 +8,9 @@ internal struct ConnectionRequestMessage : INetworkMessage public ulong ConfigHash; -#if NGO_DAMODE public bool CMBServiceConnection; public uint TickRate; public bool EnableSceneManagement; -#endif public byte[] ConnectionData; @@ -36,13 +34,12 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // END FORBIDDEN SEGMENT // ============================================================ -#if NGO_DAMODE if (CMBServiceConnection) { writer.WriteValueSafe(TickRate); writer.WriteValueSafe(EnableSceneManagement); } -#endif + if (ShouldSendConnectionData) { writer.WriteValueSafe(ConfigHash); @@ -161,19 +158,11 @@ public void Handle(ref NetworkContext context) } else { -#if NGO_DAMODE var response = new NetworkManager.ConnectionApprovalResponse { Approved = true, CreatePlayerObject = networkManager.DistributedAuthorityMode && networkManager.AutoSpawnPlayerPrefabClientSide ? false : networkManager.NetworkConfig.PlayerPrefab != null }; -#else - var response = new NetworkManager.ConnectionApprovalResponse - { - Approved = true, - CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null - }; -#endif networkManager.ConnectionManager.HandleConnectionApproval(senderId, response); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index e94f711135..6382cbb97a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -10,7 +10,6 @@ internal struct CreateObjectMessage : INetworkMessage public NetworkObject.SceneObject ObjectInfo; private FastBufferReader m_ReceivedNetworkVariableData; -#if NGO_DAMODE // DA - NGO CMB SERVICE NOTES: // The ObserverIds and ExistingObserverIds will only be populated if k_UpdateObservers is set // ObserverIds is the full list of observers (see below) @@ -83,11 +82,9 @@ private void SetFlag(bool set, byte flag) if (set) { m_CreateObjectMessageTypeFlags = (byte)(m_CreateObjectMessageTypeFlags | flag); } else { m_CreateObjectMessageTypeFlags = (byte)(m_CreateObjectMessageTypeFlags & ~flag); } } -#endif public void Serialize(FastBufferWriter writer, int targetVersion) { -#if NGO_DAMODE writer.WriteValueSafe(m_CreateObjectMessageTypeFlags); if (UpdateObservers) @@ -116,9 +113,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValuePacked(writer, NetworkObjectId); } -#else - ObjectInfo.Serialize(writer); -#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) @@ -129,7 +123,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return false; } -#if NGO_DAMODE reader.ReadValueSafe(out m_CreateObjectMessageTypeFlags); if (UpdateObservers) { @@ -172,19 +165,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return false; } m_ReceivedNetworkVariableData = reader; -#else - if (!networkManager.IsClient) - { - return false; - } - ObjectInfo.Deserialize(reader); - if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo)) - { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context); - return false; - } - m_ReceivedNetworkVariableData = reader; -#endif return true; } @@ -195,15 +175,10 @@ public void Handle(ref NetworkContext context) // If a client receives a create object message and it is still synchronizing, then defer the object creation until it has finished synchronizing if (networkManager.SceneManager.ShouldDeferCreateObject()) { -#if NGO_DAMODE networkManager.SceneManager.DeferCreateObject(context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData, ObserverIds, NewObserverIds); -#else - networkManager.SceneManager.DeferCreateObject(context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData); -#endif } else { -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode && !IncludesSerializedObject && UpdateObservers) { ObjectInfo = new NetworkObject.SceneObject() @@ -212,14 +187,9 @@ public void Handle(ref NetworkContext context) }; } CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData, ObserverIds, NewObserverIds); -#else - CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData); -#endif } } - -#if NGO_DAMODE [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void CreateObject(ref NetworkManager networkManager, ref NetworkSceneManager.DeferredObjectCreation deferredObjectCreation) { @@ -231,20 +201,13 @@ internal static void CreateObject(ref NetworkManager networkManager, ref Network var networkVariableData = deferredObjectCreation.FastBufferReader; CreateObject(ref networkManager, senderId, messageSize, sceneObject, networkVariableData, observerIds, newObserverIds); } -#endif - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#if NGO_DAMODE internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData, ulong[] observerIds, ulong[] newObserverIds) -#else - internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData) -#endif { var networkObject = (NetworkObject)null; try { -#if NGO_DAMODE if (!networkManager.DistributedAuthorityMode) { networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager); @@ -332,9 +295,6 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende } } } -#else - networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager); -#endif if (networkObject != null) { networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index b3d1d08454..b3d799a8e9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -8,7 +8,6 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp public ulong NetworkObjectId; public bool DestroyGameObject; -#if NGO_DAMODE private byte m_DestroyFlags; internal int DeferredDespawnTick; @@ -42,12 +41,10 @@ private void SetFlag(bool set, byte flag) if (set) { m_DestroyFlags = (byte)(m_DestroyFlags | flag); } else { m_DestroyFlags = (byte)(m_DestroyFlags & ~flag); } } -#endif public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, NetworkObjectId); -#if NGO_DAMODE if (IsDistributedAuthority) { writer.WriteByteSafe(m_DestroyFlags); @@ -58,7 +55,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); } -#endif writer.WriteValueSafe(DestroyGameObject); } @@ -71,7 +67,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { reader.ReadByteSafe(out m_DestroyFlags); @@ -81,23 +76,16 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); } -#endif reader.ReadValueSafe(out DestroyGameObject); if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { -#if NGO_DAMODE // Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); } -#else - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); - return false; -#endif - } return true; } @@ -107,7 +95,6 @@ public void Handle(ref NetworkContext context) var networkManager = (NetworkManager)context.SystemOwner; var networkObject = (NetworkObject)null; -#if NGO_DAMODE if (!networkManager.DistributedAuthorityMode) { // If this NetworkObject does not exist on this instance then exit early @@ -173,13 +160,7 @@ public void Handle(ref NetworkContext context) return; } } -#else - // If this NetworkObject does not exist on this instance then exit early - if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject)) - { - return; - } -#endif + if (networkObject != null) { // Otherwise just despawn the NetworkObject right now diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 22cba216b2..ed6a46946c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -37,26 +37,21 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex); -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count); } -#endif for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++) { if (!DeliveryMappedNetworkVariableIndex.Contains(i)) { -#if NGO_DAMODE // This var does not belong to the currently iterating delivery group. if (networkManager.DistributedAuthorityMode) { writer.WriteValueSafe(0); } - else -#endif - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { BytePacker.WriteValueBitPacked(writer, (ushort)0); } @@ -91,7 +86,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { shouldWrite = false; } -#if NGO_DAMODE + if (networkManager.DistributedAuthorityMode) { if (!shouldWrite) @@ -99,9 +94,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) writer.WriteValueSafe(0); } } - else -#endif - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (!shouldWrite) { @@ -130,7 +123,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } else { -#if NGO_DAMODE // DANGO-TODO: // Complex types with custom type serialization (either registered custom types or INetworkSerializable implementations) will be problematic // Non-complex types always provide a full state update per delta @@ -148,7 +140,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) writer.Seek(end_marker); } else -#endif { networkVariable.WriteDelta(writer); } @@ -192,7 +183,6 @@ public void Handle(ref NetworkContext context) } else { -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableCount); @@ -201,12 +191,10 @@ public void Handle(ref NetworkContext context) UnityEngine.Debug.LogError("Variable count mismatch"); } } -#endif for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++) { int varSize = 0; -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableSize); @@ -217,9 +205,7 @@ public void Handle(ref NetworkContext context) continue; } } - else -#endif - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize); @@ -281,11 +267,7 @@ public void Handle(ref NetworkContext context) networkBehaviour.__getTypeName(), context.MessageSize); -#if NGO_DAMODE if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety || networkManager.DistributedAuthorityMode) -#else - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) -#endif { if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize)) { @@ -314,11 +296,7 @@ public void Handle(ref NetworkContext context) // DANGO-TODO: Fix me! // When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to // log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring. -#if NGO_DAMODE networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, GetType().Name); -#else - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context); -#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index 7859ab065b..473f775841 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -33,13 +33,11 @@ public bool RemoveParent set => ByteUtility.SetBit(ref m_BitField, 2, value); } -#if NGO_DAMODE public bool AuthorityApplied { get => ByteUtility.GetBit(m_BitField, 3); set => ByteUtility.SetBit(ref m_BitField, 3, value); } -#endif // These additional properties are used to synchronize clients with the current position, // rotation, and scale after parenting/de-parenting (world/local space relative). This @@ -91,16 +89,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int reader.ReadValueSafe(out Rotation); reader.ReadValueSafe(out Scale); -#if NGO_DAMODE // If the target NetworkObject does not exist =or= the target latest parent does not exist then defer the message if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId) || (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value))) { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); -#else - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) - { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); -#endif return false; } return true; @@ -111,7 +103,6 @@ public void Handle(ref NetworkContext context) var networkManager = (NetworkManager)context.SystemOwner; var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; -#if NGO_DAMODE // For either DA or Client-Server modes, parenting is only valid if the parent was owned by a different authority (i.e. AuthorityApplied) or the sender is from the owner (DA mode) // or the server (client-server mode). networkObject.AuthorityAppliedParenting = AuthorityApplied || context.SenderId == networkObject.OwnerClientId || context.SenderId == NetworkManager.ServerClientId; @@ -120,7 +111,7 @@ public void Handle(ref NetworkContext context) NetworkLog.LogWarningServer($"Client-{context.SenderId} sent a ParentSyncMessage but is not the authority of {networkObject.gameObject.name}'s {nameof(NetworkObject)} component!"); // DANGO-TODO: Still determining if we should not apply this change (I am leaning towards not allowing it). } -#endif + networkObject.SetNetworkParenting(LatestParent, WorldPositionStays); networkObject.ApplyNetworkParenting(RemoveParent); @@ -138,7 +129,6 @@ public void Handle(ref NetworkContext context) } networkObject.transform.localScale = Scale; -#if NGO_DAMODE // If in distributed authority mode and we are running a DAHost and this is the DAHost, then forward the parent changed message to any remaining clients if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection && networkManager.DAHost) { @@ -162,7 +152,6 @@ public void Handle(ref NetworkContext context) } } } -#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs index 9efd3ef73e..c7322c499e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -34,7 +34,6 @@ public unsafe void Handle(ref NetworkContext context) var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(WrappedMessage.Metadata.NetworkObjectId, out var networkObject)) { -#if NGO_DAMODE // With distributed authority mode, we can send Rpcs before we have been notified the NetworkObject is despawned. // DANGO-TODO: Should the CMB Service cull out any Rpcs targeting recently despawned NetworkObjects? // DANGO-TODO: This would require the service to keep track of despawned NetworkObjects since we re-use NetworkObject identifiers. @@ -47,7 +46,6 @@ public unsafe void Handle(ref NetworkContext context) return; } else -#endif { throw new InvalidOperationException($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index ad822de25c..a15749e01e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -14,11 +14,7 @@ public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length); } -#if NGO_DAMODE public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload, string messageType) -#else - public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload) -#endif { ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId); @@ -27,11 +23,7 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId)) { -#if NGO_DAMODE networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context, messageType); -#else - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context); -#endif return false; } @@ -114,11 +106,7 @@ public unsafe void Serialize(FastBufferWriter writer, int targetVersion) public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { -#if NGO_DAMODE return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); -#else - return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); -#endif } public void Handle(ref NetworkContext context) @@ -153,11 +141,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { -#if NGO_DAMODE return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); -#else - return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); -#endif } public void Handle(ref NetworkContext context) @@ -195,11 +179,7 @@ public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext conte { ByteUnpacker.ReadValuePacked(reader, out SenderClientId); -#if NGO_DAMODE return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); -#else - return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); -#endif } public void Handle(ref NetworkContext context) @@ -218,7 +198,7 @@ public void Handle(ref NetworkContext context) } } -#if NGO_DAMODE + // DANGO-EXP TODO: REMOVE THIS internal struct ForwardServerRpcMessage : INetworkMessage { public int Version => 0; @@ -277,6 +257,7 @@ public void Handle(ref NetworkContext context) } + // DANGO-EXP TODO: REMOVE THIS internal struct ForwardClientRpcMessage : INetworkMessage { public int Version => 0; @@ -350,6 +331,4 @@ public void Handle(ref NetworkContext context) ClientRpcMessage.ReadBuffer.Dispose(); } } -#endif - } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs index 67d7d255c0..23f32a608f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs @@ -4,9 +4,7 @@ internal struct ServerLogMessage : INetworkMessage { public int Version => 0; -#if NGO_DAMODE public ulong SenderId; -#endif public NetworkLog.LogType LogType; // It'd be lovely to be able to replace this with FixedString or NativeArray... @@ -20,24 +18,17 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { writer.WriteValueSafe(LogType); BytePacker.WriteValuePacked(writer, Message); -#if NGO_DAMODE BytePacker.WriteValueBitPacked(writer, SenderId); -#endif } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; -#if NGO_DAMODE if ((networkManager.IsServer || networkManager.LocalClient.IsSessionOwner) && networkManager.NetworkConfig.EnableNetworkLogs) -#else - if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs) -#endif { reader.ReadValueSafe(out LogType); ByteUnpacker.ReadValuePacked(reader, out Message); -#if NGO_DAMODE ByteUnpacker.ReadValuePacked(reader, out SenderId); // If in distributed authority mode and the DAHost is not the session owner, then the DAHost will just forward the message. if (networkManager.DAHost && networkManager.CurrentSessionOwner != networkManager.LocalClientId) @@ -47,21 +38,15 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int networkManager.NetworkMetrics.TrackServerLogSent(networkManager.CurrentSessionOwner, (uint)LogType, size); return false; } -#endif return true; } - return false; } public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; -#if NGO_DAMODE var senderId = networkManager.DistributedAuthorityMode ? SenderId : context.SenderId; -#else - var senderId = context.SenderId; -#endif networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, context.MessageSize); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs index 75f7da6bdd..e4ac254dec 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SessionOwnerMessage.cs @@ -1,5 +1,3 @@ - -#if NGO_DAMODE namespace Unity.Netcode { internal struct SessionOwnerMessage : INetworkMessage @@ -26,4 +24,3 @@ public unsafe void Handle(ref NetworkContext context) } } } -#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 4fd07fa315..7dd4dae2f0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -168,7 +168,6 @@ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetwor RegisterMessageType(type); } -#if NGO_DAMODE #if UNITY_EDITOR if (EnableMessageOrderConsoleLog) { @@ -189,7 +188,6 @@ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetwor } } } -#endif #endif } catch (Exception) @@ -199,9 +197,7 @@ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetwor } } -#if NGO_DAMODE internal static bool EnableMessageOrderConsoleLog = false; -#endif public void Dispose() { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs index d4238cd0a3..30d22d4fcf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE namespace Unity.Netcode { internal class AuthorityRpcTarget : ServerRpcTarget @@ -74,4 +73,3 @@ internal AuthorityRpcTarget(NetworkManager manager) : base(manager) } } } -#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs index e7d59152dd..5001f72cf8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE namespace Unity.Netcode { internal class NotAuthorityRpcTarget : NotServerRpcTarget @@ -64,4 +63,3 @@ internal NotAuthorityRpcTarget(NetworkManager manager) : base(manager) } } } -#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index bc411dc6b4..c5706a8b31 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -61,7 +61,6 @@ public enum SendTo /// If the server is running in dedicated server mode, this is the same as . /// ClientsAndHost, -#if NGO_DAMODE /// /// Send this RPC to the authority. /// In distributed authority mode, this will be the owner of the NetworkObject. @@ -74,7 +73,6 @@ public enum SendTo /// In normal client-server mode, this is basically the exact same thing as a client rpc. /// NotAuthority, -#endif /// /// This RPC cannot be sent without passing in a target in RpcSendParams. /// @@ -114,10 +112,8 @@ internal RpcTarget(NetworkManager manager) NotMe = new NotMeRpcTarget(manager); Me = new LocalSendRpcTarget(manager); ClientsAndHost = new ClientsAndHostRpcTarget(manager); -#if NGO_DAMODE Authority = new AuthorityRpcTarget(manager); NotAuthority = new NotAuthorityRpcTarget(manager); -#endif m_CachedProxyRpcTargetGroup = new ProxyRpcTargetGroup(manager); m_CachedTargetGroup = new RpcTargetGroup(manager); m_CachedDirectSendTarget = new DirectSendRpcTarget(manager); @@ -139,10 +135,8 @@ public void Dispose() NotMe.Dispose(); Me.Dispose(); ClientsAndHost.Dispose(); -#if NGO_DAMODE Authority.Dispose(); NotAuthority.Dispose(); -#endif m_CachedProxyRpcTargetGroup.Unlock(); m_CachedTargetGroup.Unlock(); m_CachedDirectSendTarget.Unlock(); @@ -215,7 +209,6 @@ public void Dispose() /// public BaseRpcTarget ClientsAndHost; -#if NGO_DAMODE /// /// Send this RPC to the authority. /// In distributed authority mode, this will be the owner of the NetworkObject. @@ -229,7 +222,6 @@ public void Dispose() /// In normal client-server mode, this is basically the exact same thing as a client rpc. /// public BaseRpcTarget NotAuthority; -#endif /// /// Send to a specific single client ID. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 15e2026432..8ad53fb992 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -15,13 +15,11 @@ public override void Dispose() internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { -#if NGO_DAMODE if (behaviour.NetworkManager.DistributedAuthorityMode && behaviour.NetworkManager.CMBServiceConnection) { - UnityEngine.Debug.LogWarning("Sending to Server in Distributed Authority mode is not allowed!"); + UnityEngine.Debug.LogWarning("[Invalid Target] There is no server to send to when in Distributed Authority mode!"); return; } -#endif if (m_UnderlyingTarget == null) { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 3921055fd2..0c10224b14 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -131,19 +131,20 @@ public override void WriteDelta(FastBufferWriter writer) /// public override void WriteField(FastBufferWriter writer) { -#if NGO_DAMODE - writer.WriteValueSafe(NetworkVariableSerialization.Type); - if (NetworkVariableSerialization.Type == CollectionItemType.Unmanaged) + if (m_NetworkManager.DistributedAuthorityMode) { - // Write the size of the unmanaged serialized type as it has a fixed size. This allows the CMB runtime to correctly read the unmanged type. - var placeholder = new T(); - var startPos = writer.Position; - NetworkVariableSerialization.Write(writer, ref placeholder); - var size = writer.Position - startPos; - writer.Seek(startPos); - BytePacker.WriteValueBitPacked(writer, size); + writer.WriteValueSafe(NetworkVariableSerialization.Type); + if (NetworkVariableSerialization.Type == CollectionItemType.Unmanaged) + { + // Write the size of the unmanaged serialized type as it has a fixed size. This allows the CMB runtime to correctly read the unmanged type. + var placeholder = new T(); + var startPos = writer.Position; + NetworkVariableSerialization.Write(writer, ref placeholder); + var size = writer.Position - startPos; + writer.Seek(startPos); + BytePacker.WriteValueBitPacked(writer, size); + } } -#endif writer.WriteValueSafe((ushort)m_List.Length); for (int i = 0; i < m_List.Length; i++) { @@ -155,14 +156,15 @@ public override void WriteField(FastBufferWriter writer) public override void ReadField(FastBufferReader reader) { m_List.Clear(); -#if NGO_DAMODE - // Collection item type is used by the CMB rust service, drop value here. - reader.ReadValueSafe(out CollectionItemType type); - if (type == CollectionItemType.Unmanaged) + if (m_NetworkManager.DistributedAuthorityMode) { - ByteUnpacker.ReadValueBitPacked(reader, out int _); + // Collection item type is used by the CMB rust service, drop value here. + reader.ReadValueSafe(out CollectionItemType type); + if (type == CollectionItemType.Unmanaged) + { + ByteUnpacker.ReadValueBitPacked(reader, out int _); + } } -#endif reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index d625c05205..6197d750cc 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -81,20 +81,11 @@ public virtual T Value { return; } - - // DANGO-TODO: NetworkVariable permissions need to be sorted out/implemented for distributed authority mode -#if NGO_DAMODE - if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) - { - throw new InvalidOperationException($"[Client-{m_NetworkManager.LocalClientId}][{m_NetworkBehaviour.name}][{Name}] Write permissions ({WritePerm}) for this client instance is not allowed!"); - } -#else + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException($"[Client-{m_NetworkManager.LocalClientId}][{m_NetworkBehaviour.name}][{Name}] Write permissions ({WritePerm}) for this client instance is not allowed!"); } -#endif - Set(value); m_IsDisposed = false; diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 0fecff8e4a..9f4b90b543 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -50,10 +50,8 @@ public void Initialize(NetworkBehaviour networkBehaviour) if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager) { m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; -#if NGO_DAMODE // When in distributed authority mode, there is no such thing as server write permissions InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; -#endif } } @@ -78,11 +76,7 @@ protected NetworkVariableBase( NetworkVariableWritePermission writePerm = DefaultWritePerm) { ReadPerm = readPerm; -#if NGO_DAMODE InternalWritePerm = writePerm; -#else - WritePerm = writePerm; -#endif } /// @@ -102,7 +96,6 @@ protected NetworkVariableBase( /// public readonly NetworkVariableReadPermission ReadPerm; -#if NGO_DAMODE /// /// The write permission for this var /// @@ -114,15 +107,9 @@ public NetworkVariableWritePermission WritePerm } } - // DANGO TODO: We have to change the Write Permission in distributed authority. + // We had to change the Write Permission in distributed authority. // (It is too bad we initially declared it as readonly) internal NetworkVariableWritePermission InternalWritePerm; -#else - /// - /// The write permission for this var - /// - public readonly NetworkVariableWritePermission WritePerm; -#endif /// /// Sets whether or not the variable needs to be delta synced @@ -182,13 +169,11 @@ public virtual bool IsDirty() /// Whether or not the client has permission to read public bool CanClientRead(ulong clientId) { -#if NGO_DAMODE // When in distributed authority mode, everyone can read (but only the owner can write) if (m_NetworkManager != null && m_NetworkManager.DistributedAuthorityMode) { return true; } -#endif switch (ReadPerm) { default: diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs index d2142c09f4..df7c3b925e 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -1268,14 +1268,13 @@ internal static void InitializeIntegerSerialization() NetworkVariableSerialization.Serializer = new UlongSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; -#if NGO_DAMODE + // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server NetworkVariableSerialization.Type = CollectionItemType.Short; NetworkVariableSerialization.Type = CollectionItemType.UShort; NetworkVariableSerialization.Type = CollectionItemType.Int; NetworkVariableSerialization.Type = CollectionItemType.UInt; NetworkVariableSerialization.Type = CollectionItemType.Long; NetworkVariableSerialization.Type = CollectionItemType.ULong; -#endif } /// @@ -1285,9 +1284,8 @@ internal static void InitializeIntegerSerialization() public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged { NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); -#if NGO_DAMODE + // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server NetworkVariableSerialization.Type = CollectionItemType.Unmanaged; -#endif } /// @@ -1566,13 +1564,11 @@ public static class NetworkVariableSerialization { internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); - -#if NGO_DAMODE /// /// The collection item type tells the CMB server how to read the bytes of each item in the collection /// + /// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server internal static CollectionItemType Type = CollectionItemType.Unknown; -#endif /// /// A callback to check if two values are equal. diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 6df81e9741..9de36e99ca 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -18,9 +18,7 @@ internal struct SceneEntry public bool IsAssigned; public Scene Scene; } -#if NGO_DAMODE public bool IsIntegrationTest() { return false; } -#endif internal Dictionary> SceneNameToSceneHandles = new Dictionary>(); @@ -330,12 +328,8 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode) { var sceneManager = networkManager.SceneManager; -#if NGO_DAMODE // Don't let non-authority set this value if ((!networkManager.DistributedAuthorityMode && !networkManager.IsServer) || (networkManager.DistributedAuthorityMode && !networkManager.LocalClient.IsSessionOwner)) -#else - if (!networkManager.IsServer) -#endif { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs index 4df6c4f188..9cd3ed1ac8 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs @@ -32,8 +32,7 @@ internal interface ISceneManagerHandler void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode); bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager); -#if NGO_DAMODE + bool IsIntegrationTest(); -#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 2a4caa20f4..b305fc789a 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -423,7 +423,6 @@ public bool ActiveSceneSynchronizationEnabled /// internal Dictionary ScenesLoaded = new Dictionary(); -#if NGO_DAMODE /// /// Returns the currently loaded scenes that are synchronized with the session owner or server depending upon the selected /// NetworkManager session mode. @@ -437,7 +436,6 @@ public List GetSynchronizedScenes() { return ScenesLoaded.Values.ToList(); } -#endif /// /// Since Scene.handle is unique per client, we create a look-up table between the client and server to associate server unique scene @@ -566,7 +564,6 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) /// private bool m_DisableValidationWarningMessages; -#if NGO_DAMODE internal bool HasSceneAuthority() { if (!NetworkManager) @@ -576,9 +573,6 @@ internal bool HasSceneAuthority() return (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer) || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner); } -#endif - - /// /// Handle NetworkSeneManager clean up /// @@ -643,11 +637,7 @@ internal void EndSceneEvent(uint sceneEventId) internal bool ShouldDeferCreateObject() { // This applies only to remote clients and when scene management is enabled -#if NGO_DAMODE if (!NetworkManager.NetworkConfig.EnableSceneManagement || HasSceneAuthority()) -#else - if (!NetworkManager.NetworkConfig.EnableSceneManagement || NetworkManager.IsServer) -#endif { return false; } @@ -819,11 +809,7 @@ internal NetworkSceneManager(NetworkManager networkManager) // server side when the NetworkManager is started and NetworkSceneManager instantiated when // scene management is enabled. -#if NGO_DAMODE if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkManager.NetworkConfig.EnableSceneManagement) -#else - if (networkManager.IsServer && networkManager.NetworkConfig.EnableSceneManagement) -#endif { for (int i = 0; i < SceneManager.sceneCount; i++) { @@ -837,7 +823,6 @@ internal NetworkSceneManager(NetworkManager networkManager) UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene); } -#if NGO_DAMODE internal void InitializeScenesLoaded() { if (!NetworkManager.DistributedAuthorityMode) @@ -855,19 +840,16 @@ internal void InitializeScenesLoaded() SceneManagerHandler.PopulateLoadedScenes(ref ScenesLoaded, NetworkManager); } } -#endif /// /// Synchronizes clients when the currently active scene is changed /// private void SceneManager_ActiveSceneChanged(Scene current, Scene next) { -#if NGO_DAMODE if ((!NetworkManager.DistributedAuthorityMode && !NetworkManager.IsServer) || (NetworkManager.DistributedAuthorityMode && !NetworkManager.LocalClient.IsSessionOwner)) { return; } -#endif // If no clients are connected, then don't worry about notifications if (!(NetworkManager.ConnectedClientsIds.Count > (NetworkManager.IsHost ? 1 : 0))) @@ -891,16 +873,12 @@ private void SceneManager_ActiveSceneChanged(Scene current, Scene next) var sceneEvent = BeginSceneEvent(); sceneEvent.SceneEventType = SceneEventType.ActiveSceneChanged; sceneEvent.ActiveSceneHash = BuildIndexToHash[next.buildIndex]; -#if NGO_DAMODE var sessionOwner = NetworkManager.ServerClientId; if (NetworkManager.DistributedAuthorityMode) { sessionOwner = NetworkManager.CurrentSessionOwner; } SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != sessionOwner).ToArray()); -#else - SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray()); -#endif EndSceneEvent(sceneEvent.SceneEventId); } } @@ -933,20 +911,11 @@ internal bool ValidateSceneBeforeLoading(int sceneIndex, string sceneName, LoadS } if (!validated && !m_DisableValidationWarningMessages) { - -#if NGO_DAMODE var serverHostorClient = "Client"; if (HasSceneAuthority()) { serverHostorClient = NetworkManager.DistributedAuthorityMode ? "Session Owner" : NetworkManager.IsHost ? "Host" : "Server"; } -#else - var serverHostorClient = "Client"; - if (NetworkManager.IsServer) - { - serverHostorClient = NetworkManager.IsHost ? "Host" : "Server"; - } -#endif Debug.LogWarning($"Scene {sceneName} of Scenes in Build Index {sceneIndex} being loaded in {loadSceneMode} mode failed validation on the {serverHostorClient}!"); } return validated; @@ -1058,11 +1027,7 @@ internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdH if (ScenePlacedObjects.ContainsKey(globalObjectIdHash)) { var sceneHandle = SceneBeingSynchronized.handle; -#if NGO_DAMODE if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0 && ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle.Value)) -#else - if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0) -#endif { sceneHandle = ServerSceneHandleToClientSceneHandle[networkSceneHandle.Value]; } @@ -1087,7 +1052,6 @@ private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds) return; } var sceneEvent = SceneEventDataStore[sceneEventId]; -#if NGO_DAMODE sceneEvent.SenderClientId = NetworkManager.LocalClientId; if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) @@ -1104,7 +1068,6 @@ private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds) } } else -#endif { var message = new SceneEventMessage { @@ -1128,7 +1091,7 @@ private SceneEventProgress ValidateSceneEventUnloading(Scene scene) Debug.LogWarning($"{nameof(LoadScene)} was called, but {nameof(NetworkConfig.EnableSceneManagement)} was not enabled! Enable {nameof(NetworkConfig.EnableSceneManagement)} prior to starting a client, host, or server prior to using {nameof(NetworkSceneManager)}!"); return new SceneEventProgress(null, SceneEventProgressStatus.SceneManagementNotEnabled); } -#if NGO_DAMODE + if (!HasSceneAuthority()) { if (NetworkManager.DistributedAuthorityMode) @@ -1143,14 +1106,6 @@ private SceneEventProgress ValidateSceneEventUnloading(Scene scene) } } -#else - if (!NetworkManager.IsServer) - { - Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Unload] Clients cannot invoke the {nameof(UnloadScene)} method!"); - return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction); - } -#endif - if (!scene.isLoaded) { Debug.LogWarning($"{nameof(UnloadScene)} was called, but the scene {scene.name} is not currently loaded!"); @@ -1172,7 +1127,7 @@ private SceneEventProgress ValidateSceneEventLoading(string sceneName) Debug.LogWarning($"{nameof(LoadScene)} was called, but {nameof(NetworkConfig.EnableSceneManagement)} was not enabled! Enable {nameof(NetworkConfig.EnableSceneManagement)} prior to starting a client, host, or server prior to using {nameof(NetworkSceneManager)}!"); return new SceneEventProgress(null, SceneEventProgressStatus.SceneManagementNotEnabled); } -#if NGO_DAMODE + if (!HasSceneAuthority()) { if (NetworkManager.DistributedAuthorityMode) @@ -1186,16 +1141,6 @@ private SceneEventProgress ValidateSceneEventLoading(string sceneName) return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction); } } -#else - if (!NetworkManager.IsServer) - { - - Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Load] Clients cannot invoke the {nameof(LoadScene)} method!"); - return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction); - } -#endif - - return ValidateSceneEvent(sceneName); } @@ -1251,14 +1196,11 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode; sceneEventData.ClientsTimedOut = clientsThatTimedOut; -#if NGO_DAMODE - if (NetworkManager.DistributedAuthorityMode) { SendSceneEventData(sceneEventData.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); } else -#endif { var message = new SceneEventMessage { @@ -1273,7 +1215,6 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress size); } -#if NGO_DAMODE // Send a local notification to the session owner that all clients are done loading or unloading OnSceneEvent?.Invoke(new SceneEvent() { @@ -1284,18 +1225,6 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress ClientsThatCompleted = clientsThatCompleted, ClientsThatTimedOut = clientsThatTimedOut, }); -#else - // Send a local notification to the server that all clients are done loading or unloading - OnSceneEvent?.Invoke(new SceneEvent() - { - SceneEventType = sceneEventProgress.SceneEventType, - SceneName = SceneNameFromHash(sceneEventProgress.SceneHash), - ClientId = NetworkManager.ServerClientId, - LoadSceneMode = sceneEventProgress.LoadSceneMode, - ClientsThatCompleted = clientsThatCompleted, - ClientsThatTimedOut = clientsThatTimedOut, - }); -#endif if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted) { @@ -1341,7 +1270,6 @@ public SceneEventProgressStatus UnloadScene(Scene scene) return SceneEventProgressStatus.InternalNetcodeError; } -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { if (ClientSceneHandleToServerSceneHandle.ContainsKey(sceneHandle)) @@ -1349,7 +1277,6 @@ public SceneEventProgressStatus UnloadScene(Scene scene) sceneHandle = ClientSceneHandleToServerSceneHandle[sceneHandle]; } } -#endif // 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 @@ -1370,7 +1297,6 @@ public SceneEventProgressStatus UnloadScene(Scene scene) sceneEventProgress.SceneEventId = sceneEventData.SceneEventId; sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded; -#if NGO_DAMODE if (!RemoveServerClientSceneHandle(sceneEventData.SceneHandle, scene.handle)) { Debug.LogError($"Failed to remove {SceneNameFromHash(sceneEventData.SceneHash)} scene handles [Server ({sceneEventData.SceneHandle})][Local({scene.handle})]"); @@ -1389,20 +1315,7 @@ public SceneEventProgressStatus UnloadScene(Scene scene) }); OnUnload?.Invoke(NetworkManager.LocalClientId, sceneName, sceneUnload); -#else - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); - // Notify local server that a scene is going to be unloaded - OnSceneEvent?.Invoke(new SceneEvent() - { - AsyncOperation = sceneUnload, - SceneEventType = sceneEventData.SceneEventType, - LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = sceneName, - ClientId = NetworkManager.ServerClientId // Server can only invoke this - }); - OnUnload?.Invoke(NetworkManager.ServerClientId, sceneName, sceneUnload); -#endif //Return the status return sceneEventProgress.Status; } @@ -1446,12 +1359,10 @@ private void OnClientUnloadScene(uint sceneEventId) OnSceneEventCompleted = OnSceneUnloaded, }; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { SceneEventProgressTracking.Add(sceneEventData.SceneEventProgressId, sceneEventProgress); } -#endif var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); @@ -1486,9 +1397,7 @@ private void OnSceneUnloaded(uint sceneEventId) // If we are shutdown or about to shutdown, then ignore this event if (!NetworkManager.IsListening || NetworkManager.ShutdownInProgress) { -#if NGO_DAMODE EndSceneEvent(sceneEventId); -#endif return; } @@ -1496,38 +1405,26 @@ private void OnSceneUnloaded(uint sceneEventId) MoveObjectsFromDontDestroyOnLoadToScene(SceneManager.GetActiveScene()); var sceneEventData = SceneEventDataStore[sceneEventId]; -#if NGO_DAMODE if (HasSceneAuthority()) { var sessionOwner = NetworkManager.DistributedAuthorityMode ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; -#else - // First thing we do, if we are a server, is to send the unload scene event. - if (NetworkManager.IsServer) - { - var sessionOwner = NetworkManager.ServerClientId; -#endif // Server sends the unload scene notification after unloading because it will despawn all scene relative in-scene NetworkObjects // If we send this event to all clients before the server is finished unloading they will get warning about an object being // despawned that no longer exists SendSceneEventData(sceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != sessionOwner).ToArray()); -#if NGO_DAMODE + //Only if we are session owner do we want register having loaded for the associated SceneEventProgress if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && HasSceneAuthority()) -#else - if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsServer) -#endif { SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(sessionOwner); } } -#if NGO_DAMODE else if (NetworkManager.DistributedAuthorityMode) { SceneEventProgressTracking.Remove(sceneEventData.SceneEventProgressId); m_IsSceneEventActive = false; } -#endif // Next we prepare to send local notifications for unload complete sceneEventData.SceneEventType = SceneEventType.UnloadComplete; @@ -1538,16 +1435,11 @@ private void OnSceneUnloaded(uint sceneEventId) SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, SceneName = SceneNameFromHash(sceneEventData.SceneHash), -#if NGO_DAMODE ClientId = NetworkManager.LocalClientId, -#else - ClientId = NetworkManager.IsServer ? NetworkManager.ServerClientId : NetworkManager.LocalClientId -#endif }); OnUnloadComplete?.Invoke(NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash)); -#if NGO_DAMODE if (!HasSceneAuthority()) { sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; @@ -1562,13 +1454,6 @@ private void OnSceneUnloaded(uint sceneEventId) var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); } -#else - // Clients send a notification back to the server they have completed the unload scene event - if (!NetworkManager.IsServer) - { - SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); - } -#endif EndSceneEvent(sceneEventId); // This scene event is now considered "complete" m_IsSceneEventActive = false; @@ -1599,14 +1484,14 @@ internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) SceneEventId = sceneEventId, OnSceneEventCompleted = EmptySceneUnloadedOperation }; -#if NGO_DAMODE + if (ClientSceneHandleToServerSceneHandle.ContainsKey(keyHandleEntry.Value.handle)) { var serverSceneHandle = ClientSceneHandleToServerSceneHandle[keyHandleEntry.Value.handle]; ServerSceneHandleToClientSceneHandle.Remove(serverSceneHandle); } ClientSceneHandleToServerSceneHandle.Remove(keyHandleEntry.Value.handle); -#endif + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress); SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload); @@ -1680,7 +1565,6 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded; var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); -#if NGO_DAMODE // Notify the local server that a scene loading event has begun OnSceneEvent?.Invoke(new SceneEvent() { @@ -1691,18 +1575,6 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc ClientId = NetworkManager.LocalClientId }); OnLoad?.Invoke(NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); -#else - // Notify the local server that a scene loading event has begun - OnSceneEvent?.Invoke(new SceneEvent() - { - AsyncOperation = sceneLoad, - SceneEventType = sceneEventData.SceneEventType, - LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = sceneName, - ClientId = NetworkManager.ServerClientId - }); - OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); -#endif //Return our scene progress instance return sceneEventProgress.Status; @@ -1723,11 +1595,7 @@ internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scen { s_Instances.Add(networkManager, new List()); } -#if NGO_DAMODE var clientId = networkManager.LocalClientId; -#else - var clientId = networkManager.IsServer ? NetworkManager.ServerClientId : networkManager.LocalClientId; -#endif s_Instances[networkManager].Add(new SceneUnloadEventHandler(networkSceneManager, scene, clientId, loadSceneMode, asyncOperation)); } @@ -1866,13 +1734,11 @@ private void OnClientSceneLoadingEvent(uint sceneEventId) Status = SceneEventProgressStatus.Started, }; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { SceneEventProgressTracking.Add(sceneEventData.SceneEventProgressId, sceneEventProgress); m_IsSceneEventActive = true; } -#endif var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress); OnSceneEvent?.Invoke(new SceneEvent() @@ -1896,9 +1762,7 @@ private void OnSceneLoaded(uint sceneEventId) // If we are shutdown or about to shutdown, then ignore this event if (!NetworkManager.IsListening || NetworkManager.ShutdownInProgress) { -#if NGO_DAMODE EndSceneEvent(sceneEventId); -#endif return; } @@ -1914,7 +1778,6 @@ private void OnSceneLoaded(uint sceneEventId) SceneManager.SetActiveScene(nextScene); } -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { var networkSceneHandle = nextScene.handle; @@ -1939,7 +1802,6 @@ private void OnSceneLoaded(uint sceneEventId) Debug.LogWarning($"Server Scene Handle ({nextScene.handle}) already exist! Happened during scene load of {nextScene.name} with the local handle ({nextScene.handle})"); } } -#endif //Get all NetworkObjects loaded by the scene PopulateScenePlacedObjects(nextScene); @@ -1956,21 +1818,14 @@ private void OnSceneLoaded(uint sceneEventId) // When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do // not destroy temporary scene are moved into the active scene IsSpawnedObjectsPendingInDontDestroyOnLoad = false; -#if NGO_DAMODE + if (HasSceneAuthority()) { OnSessionOwnerLoadedScene(sceneEventId, nextScene); -#else - if (NetworkManager.IsServer) - { - OnServerLoadedScene(sceneEventId, nextScene); -#endif } else { -#if NGO_DAMODE if (!NetworkManager.DistributedAuthorityMode) -#endif { // For the client, we make a server scene handle to client scene handle look up table if (!UpdateServerClientSceneHandle(sceneEventData.SceneHandle, nextScene.handle, nextScene)) @@ -1987,11 +1842,7 @@ private void OnSceneLoaded(uint sceneEventId) /// Server/SessionOwner side: /// On scene loaded callback method invoked by OnSceneLoading only /// -#if NGO_DAMODE private void OnSessionOwnerLoadedScene(uint sceneEventId, Scene scene) -#else - private void OnServerLoadedScene(uint sceneEventId, Scene scene) -#endif { var sceneEventData = SceneEventDataStore[sceneEventId]; // Register in-scene placed NetworkObjects with spawn manager @@ -2001,15 +1852,9 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) { if (!keyValuePairBySceneHandle.Value.IsPlayerObject) { -#if NGO_DAMODE // All in-scene placed NetworkObjects default to being owned by the server NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, NetworkManager.LocalClientId, true); -#else - // All in-scene placed NetworkObjects default to being owned by the server - NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, - NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, NetworkManager.ServerClientId, true); -#endif } } } @@ -2020,7 +1865,6 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) // Set the server's scene's handle so the client can build a look up table sceneEventData.SceneHandle = scene.handle; -#if NGO_DAMODE var sessionOwner = NetworkManager.ServerClientId; // Send all clients the scene load event if (NetworkManager.DistributedAuthorityMode) @@ -2048,41 +1892,6 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) { SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.LocalClientId); } -#else - // Send all clients the scene load event - for (int j = 0; j < NetworkManager.ConnectedClientsList.Count; j++) - { - var clientId = NetworkManager.ConnectedClientsList[j].ClientId; - if (clientId != NetworkManager.ServerClientId) - { - sceneEventData.TargetClientId = clientId; - var message = new SceneEventMessage - { - EventData = sceneEventData - }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, clientId); - NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEventData.SceneEventType, scene.name, size); - } - } - m_IsSceneEventActive = false; - //First, notify local server that the scene was loaded - OnSceneEvent?.Invoke(new SceneEvent() - { - SceneEventType = SceneEventType.LoadComplete, - LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = SceneNameFromHash(sceneEventData.SceneHash), - ClientId = NetworkManager.ServerClientId, - Scene = scene, - }); - - OnLoadComplete?.Invoke(NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); - - //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress - if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsHost) - { - SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId); - } -#endif EndSceneEvent(sceneEventId); } @@ -2097,7 +1906,6 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) sceneEventData.SceneEventType = SceneEventType.LoadComplete; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; @@ -2111,7 +1919,6 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); } else -#endif { SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); } @@ -2121,13 +1928,11 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) // Process any pending create object messages that the client received while loading a scene ProcessDeferredCreateObjectMessages(); -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { SceneEventProgressTracking.Remove(sceneEventData.SceneEventProgressId); m_IsSceneEventActive = false; } -#endif // Notify local client that the scene was loaded OnSceneEvent?.Invoke(new SceneEvent() @@ -2203,7 +2008,6 @@ internal void SynchronizeNetworkObjects(ulong clientId) } sceneEventData.SceneHash = SceneHashFromNameOrPath(scene.path); -#if NGO_DAMODE // If we are just a normal client, then always use the server scene handle if (NetworkManager.DistributedAuthorityMode) { @@ -2211,7 +2015,6 @@ internal void SynchronizeNetworkObjects(ulong clientId) sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[scene.handle]; } else -#endif { sceneEventData.SceneHandle = scene.handle; } @@ -2220,14 +2023,13 @@ internal void SynchronizeNetworkObjects(ulong clientId) { continue; } -#if NGO_DAMODE + // If we are just a normal client and in distributed authority mode, then always use the known server scene handle if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) { sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); } else -#endif { sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); } @@ -2241,13 +2043,11 @@ internal void SynchronizeNetworkObjects(ulong clientId) }; var size = 0; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) { size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId); } else -#endif { size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, clientId); } @@ -2391,14 +2191,12 @@ private void ClientLoadedSynchronization(uint sceneEventId) responseSceneEventData.SceneHash = sceneEventData.ClientSceneHash; var target = NetworkManager.ServerClientId; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { responseSceneEventData.SenderClientId = NetworkManager.LocalClientId; responseSceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; } -#endif var message = new SceneEventMessage { EventData = responseSceneEventData @@ -2492,9 +2290,6 @@ private void HandleClientSceneEvent(uint sceneEventId) } case SceneEventType.ObjectSceneChanged: { -#if !NGO_DAMODE - MigrateNetworkObjectsIntoScenes(); -#endif EndSceneEvent(sceneEventId); break; } @@ -2539,7 +2334,6 @@ private void HandleClientSceneEvent(uint sceneEventId) ProcessDeferredCreateObjectMessages(); sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; @@ -2553,7 +2347,6 @@ private void HandleClientSceneEvent(uint sceneEventId) NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); } else -#endif { SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId }); } @@ -2561,7 +2354,6 @@ private void HandleClientSceneEvent(uint sceneEventId) // All scenes are synchronized, let the server know we are done synchronizing NetworkManager.IsConnectedClient = true; -#if NGO_DAMODE // With distributed authority, either the client-side automatically spawns the default assigned player prefab or // if AutoSpawnPlayerPrefabClientSide is disabled the client-side will determine what player prefab to spawn and // when it gets spawned. @@ -2569,7 +2361,6 @@ private void HandleClientSceneEvent(uint sceneEventId) { NetworkManager.ConnectionManager.CreateAndSpawnPlayer(NetworkManager.LocalClientId); } -#endif // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); @@ -2651,20 +2442,11 @@ private void HandleClientSceneEvent(uint sceneEventId) } } - -#if NGO_DAMODE /// /// Session Owner Side: /// Handles incoming Scene_Event messages for the current session owner /// private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) -#else - /// - /// Server Side: - /// Handles incoming Scene_Event messages for host or server - /// - private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) -#endif { var sceneEventData = SceneEventDataStore[sceneEventId]; switch (sceneEventData.SceneEventType) @@ -2714,7 +2496,6 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) // At this point the client is considered fully "connected" -#if NGO_DAMODE if ((NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) || !NetworkManager.DistributedAuthorityMode) { if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) @@ -2739,16 +2520,6 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) EndSceneEvent(sceneEventId); return; } -#else - // Notify the local server that a client has finished synchronizing - OnSceneEvent?.Invoke(new SceneEvent() - { - SceneEventType = sceneEventData.SceneEventType, - SceneName = string.Empty, - ClientId = clientId - }); - NetworkManager.ConnectedClients[clientId].IsConnected = true; -#endif // All scenes are synchronized, let the server know we are done synchronizing OnSynchronizeComplete?.Invoke(clientId); @@ -2780,9 +2551,8 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) ClientId = clientId }); } -#if NGO_DAMODE + // DANGO-EXP TODO: Remove this once service distributes objects NetworkManager.SpawnManager.DistributeNetworkObjects(clientId); -#endif EndSceneEvent(sceneEventId); break; } @@ -2794,13 +2564,10 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) } } - // TODO: See if this short circuit is still needed post scene management implementation. It's possible that these tests will become superfluous. -#if NGO_DAMODE /// /// Skips scene handling to be able to test CMB DA_NGO Codec tests /// internal bool SkipSceneHandling; -#endif /// /// Both Client and Server: Incoming scene event entry point @@ -2813,11 +2580,11 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) { var sceneEventData = BeginSceneEvent(); sceneEventData.Deserialize(reader); -#if NGO_DAMODE if (SkipSceneHandling) { return; } + // DA HOST Will keep track of session owner and if it is not the scene owner it will forward the message // to the current session owner if (NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) @@ -2859,7 +2626,7 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) } } } -#endif + NetworkManager.NetworkMetrics.TrackSceneEventReceived( clientId, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), reader.Length); @@ -2886,16 +2653,12 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) } else { -#if NGO_DAMODE var sendingClient = clientId; if (NetworkManager.DistributedAuthorityMode) { sendingClient = sceneEventData.SenderClientId; } HandleSessionOwnerEvent(sceneEventData.SceneEventId, sendingClient); -#else - HandleServerSceneEvent(sceneEventData.SceneEventId, clientId); -#endif } } else @@ -2927,19 +2690,13 @@ internal void MoveObjectsToDontDestroyOnLoad() if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); -#if NGO_DAMODE // When temporarily migrating to the DDOL, adjust the network and origin scene handles so no messages are generated // about objects being moved to a new scene. networkObject.NetworkSceneHandle = ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; -#endif } } -#if NGO_DAMODE else if (networkObject.HasAuthority) -#else - else if (NetworkManager.IsServer) -#endif { networkObject.Despawn(); } @@ -3019,7 +2776,6 @@ internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) // back into the currently active scene if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { // When migrating out of the DDOL to the currently active scene, adjust the network and origin scene handles so no messages are generated @@ -3034,7 +2790,6 @@ internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) } networkObject.SceneOriginHandle = scene.handle; } -#endif SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); } @@ -3046,11 +2801,7 @@ internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) /// Holds a list of scene handles (server-side relative) and NetworkObjects migrated into it /// during the current frame. /// -#if NGO_DAMODE internal Dictionary>> ObjectsMigratedIntoNewScene = new Dictionary>>(); -#else - internal Dictionary> ObjectsMigratedIntoNewScene = new Dictionary>(); -#endif internal bool IsSceneEventInProgress() { @@ -3075,11 +2826,7 @@ internal bool IsSceneEventInProgress() internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) { // Really, this should never happen but in case it does -#if NGO_DAMODE if (!networkObject.HasAuthority) -#else - if (!NetworkManager.IsServer) -#endif { if (NetworkManager.LogLevel == LogLevel.Developer) { @@ -3112,7 +2859,7 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) { return; } -#if NGO_DAMODE + // Otherwise, add the NetworkObject into the list of NetworkObjects who's scene has changed if (!ObjectsMigratedIntoNewScene.ContainsKey(networkObject.NetworkSceneHandle)) { @@ -3125,14 +2872,6 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) } ObjectsMigratedIntoNewScene[networkObject.NetworkSceneHandle][NetworkManager.LocalClientId].Add(networkObject); -#else - // Otherwise, add the NetworkObject into the list of NetworkObjects who's scene has changed - if (!ObjectsMigratedIntoNewScene.ContainsKey(networkObject.gameObject.scene.handle)) - { - ObjectsMigratedIntoNewScene.Add(networkObject.gameObject.scene.handle, new List()); - } - ObjectsMigratedIntoNewScene[networkObject.gameObject.scene.handle].Add(networkObject); -#endif } /// @@ -3140,8 +2879,6 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) /// or invoked by when a client finishes /// synchronization. /// - -#if NGO_DAMODE internal void MigrateNetworkObjectsIntoScenes() { try @@ -3175,40 +2912,7 @@ internal void MigrateNetworkObjectsIntoScenes() { NetworkLog.LogErrorServer($"{ex.Message}\n Stack Trace:\n {ex.StackTrace}"); } - - // Clear out the list once complete - //ObjectsMigratedIntoNewScene.Clear(); } -#else - internal void MigrateNetworkObjectsIntoScenes() - { - try - { - foreach (var sceneEntry in ObjectsMigratedIntoNewScene) - { - if (ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEntry.Key)) - { - var clientSceneHandle = ServerSceneHandleToClientSceneHandle[sceneEntry.Key]; - if (ScenesLoaded.ContainsKey(ServerSceneHandleToClientSceneHandle[sceneEntry.Key])) - { - var scene = ScenesLoaded[clientSceneHandle]; - foreach (var networkObject in sceneEntry.Value) - { - SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); - } - } - } - } - } - catch (Exception ex) - { - NetworkLog.LogErrorServer($"{ex.Message}\n Stack Trace:\n {ex.StackTrace}"); - } - - // Clear out the list once complete - ObjectsMigratedIntoNewScene.Clear(); - } -#endif private List m_ScenesToRemoveFromObjectMigration = new List(); @@ -3218,8 +2922,6 @@ internal void MigrateNetworkObjectsIntoScenes() /// internal void CheckForAndSendNetworkObjectSceneChanged() { - -#if NGO_DAMODE // Early exit if not the server or there is nothing pending if (ObjectsMigratedIntoNewScene.Count == 0) { @@ -3277,61 +2979,15 @@ internal void CheckForAndSendNetworkObjectSceneChanged() ObjectsMigratedIntoNewScene.Clear(); return; } -#else - // Early exit if not the server or there is nothing pending - if (!NetworkManager.IsServer || ObjectsMigratedIntoNewScene.Count == 0) - { - return; - } - // Double check that the NetworkObjects to migrate still exist - m_ScenesToRemoveFromObjectMigration.Clear(); - foreach (var sceneEntry in ObjectsMigratedIntoNewScene) - { - for (int i = sceneEntry.Value.Count - 1; i >= 0; i--) - { - // Remove NetworkObjects that are no longer spawned - if (!sceneEntry.Value[i].IsSpawned) - { - sceneEntry.Value.RemoveAt(i); - } - } - // If the scene entry no longer has any NetworkObjects to migrate - // then add it to the list of scenes to be removed from the table - // of scenes containing NetworkObjects to migrate. - if (sceneEntry.Value.Count == 0) - { - m_ScenesToRemoveFromObjectMigration.Add(sceneEntry.Key); - } - } - - // Remove sceneHandle entries that no longer have any NetworkObjects remaining - foreach (var sceneHandle in m_ScenesToRemoveFromObjectMigration) - { - ObjectsMigratedIntoNewScene.Remove(sceneHandle); - } - // If there is nothing to send a migration notification for then exit - if (ObjectsMigratedIntoNewScene.Count == 0) - { - return; - } -#endif // Some NetworkObjects still exist, send the message var sceneEvent = BeginSceneEvent(); sceneEvent.SceneEventType = SceneEventType.ObjectSceneChanged; -#if NGO_DAMODE SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); ObjectsMigratedIntoNewScene.Clear(); -#else - SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray()); -#endif - EndSceneEvent(sceneEvent.SceneEventId); - - } -#if NGO_DAMODE // Used to handle client-side scene migration messages received while // a client is synchronizing internal struct DeferredObjectsMovedEvent @@ -3340,23 +2996,14 @@ internal struct DeferredObjectsMovedEvent internal Dictionary> ObjectsMigratedTable; } internal List DeferredObjectsMovedEvents = new List(); -#else - internal struct DeferredObjectsMovedEvent - { - internal Dictionary> ObjectsMigratedTable; - } - internal List DeferredObjectsMovedEvents = new List(); -#endif internal struct DeferredObjectCreation { internal ulong SenderId; internal uint MessageSize; -#if NGO_DAMODE // When we transfer session owner and we are using a DAHost, this will be pertinent (otherwise it is not when connected to a DA service) internal ulong[] ObserverIds; internal ulong[] NewObserverIds; -#endif internal NetworkObject.SceneObject SceneObject; internal FastBufferReader FastBufferReader; } @@ -3364,21 +3011,15 @@ internal struct DeferredObjectCreation internal List DeferredObjectCreationList = new List(); internal int DeferredObjectCreationCount; -#if NGO_DAMODE // The added clientIds is specific to DAHost when session ownership changes and a normal client is controlling scene loading internal void DeferCreateObject(ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader fastBufferReader, ulong[] observerIds, ulong[] newObserverIds) -#else - internal void DeferCreateObject(ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader fastBufferReader) -#endif { var deferredObjectCreationEntry = new DeferredObjectCreation() { SenderId = senderId, MessageSize = messageSize, -#if NGO_DAMODE ObserverIds = observerIds, NewObserverIds = newObserverIds, -#endif SceneObject = sceneObject, }; @@ -3402,11 +3043,7 @@ private void ProcessDeferredCreateObjectMessages() for (int i = 0; i < DeferredObjectCreationList.Count; i++) { var deferredObjectCreation = DeferredObjectCreationList[i]; -#if NGO_DAMODE CreateObjectMessage.CreateObject(ref networkManager, ref deferredObjectCreation); -#else - CreateObjectMessage.CreateObject(ref networkManager, deferredObjectCreation.SenderId, deferredObjectCreation.MessageSize, deferredObjectCreation.SceneObject, deferredObjectCreation.FastBufferReader); -#endif } DeferredObjectCreationCount = DeferredObjectCreationList.Count; DeferredObjectCreationList.Clear(); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index cbcb736e98..e27f265ee9 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -247,9 +247,7 @@ internal void InitializeForSynch() { SceneHandlesToSynchronize.Clear(); } -#if NGO_DAMODE ForwardSynchronization = false; -#endif } /// @@ -464,9 +462,7 @@ private void LogArray(byte[] data, int start = 0, int stop = 0, StringBuilder bu } } -#if NGO_DAMODE internal bool ForwardSynchronization; -#endif /// /// Client and Server Side: @@ -478,13 +474,11 @@ internal void Serialize(FastBufferWriter writer) // Write the scene event type writer.WriteValueSafe(SceneEventType); -#if NGO_DAMODE if (m_NetworkManager.DistributedAuthorityMode) { BytePacker.WriteValueBitPacked(writer, TargetClientId); BytePacker.WriteValueBitPacked(writer, SenderClientId); } -#endif if (SceneEventType == SceneEventType.ActiveSceneChanged) { @@ -531,13 +525,11 @@ internal void Serialize(FastBufferWriter writer) } case SceneEventType.Load: { -#if NGO_DAMODE if (m_NetworkManager.DistributedAuthorityMode && IsForwarding && m_NetworkManager.DAHost) { CopyInternalBuffer(ref writer); } else -#endif { SerializeScenePlacedObjects(writer); } @@ -562,12 +554,10 @@ internal void Serialize(FastBufferWriter writer) } } -#if NGO_DAMODE private unsafe void CopyInternalBuffer(ref FastBufferWriter writer) { writer.WriteBytesSafe(InternalBuffer.GetUnsafePtrAtCurrentPosition(), InternalBuffer.Length); } -#endif /// /// Server Side: @@ -588,7 +578,6 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) // Store our current position in the stream to come back and say how much data we have written var positionStart = writer.Position; -#if NGO_DAMODE if (m_NetworkManager.DistributedAuthorityMode && ForwardSynchronization && m_NetworkManager.DAHost) { writer.WriteValueSafe(m_InternalBufferSize); @@ -599,9 +588,6 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) } return; } -#endif - - // Size Place Holder -- Start // !!NOTE!!: Since this is a placeholder to be set after we know how much we have written, @@ -615,20 +601,16 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) { builder.AppendLine($"[Synchronize Objects][positionStart: {positionStart}][WPos: {writer.Position}][NO-Count: {m_NetworkObjectsSync.Count}] Begin:"); } -#if NGO_DAMODE var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; -#endif + // Serialize all NetworkObjects that are spawned for (var i = 0; i < m_NetworkObjectsSync.Count; ++i) { var networkObject = m_NetworkObjectsSync[i]; var noStart = writer.Position; -#if NGO_DAMODE // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId, distributedAuthority); -#else - var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId); -#endif + sceneObject.Serialize(writer); var noStop = writer.Position; totalBytes += noStop - noStart; @@ -724,13 +706,12 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) internal void Deserialize(FastBufferReader reader) { reader.ReadValueSafe(out SceneEventType); -#if NGO_DAMODE if (m_NetworkManager.DistributedAuthorityMode) { ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId); ByteUnpacker.ReadValueBitPacked(reader, out SenderClientId); } -#endif + if (SceneEventType == SceneEventType.ActiveSceneChanged) { reader.ReadValueSafe(out ActiveSceneHash); @@ -809,9 +790,7 @@ internal void Deserialize(FastBufferReader reader) } } -#if NGO_DAMODE private int m_InternalBufferSize; -#endif /// /// Client Side: @@ -829,9 +808,7 @@ internal void CopySceneSynchronizationData(FastBufferReader reader) // is not packed! reader.ReadValueSafe(out int sizeToCopy); -#if NGO_DAMODE m_InternalBufferSize = sizeToCopy; -#endif unsafe { @@ -1206,11 +1183,9 @@ internal void ReadSceneEventProgressDone(FastBufferReader reader) /// Serialize scene handles and associated NetworkObjects that were migrated /// into a new scene. /// -#if NGO_DAMODE internal bool IsForwarding; private ulong m_OwnerId; - private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) { var sceneManager = m_NetworkManager.SceneManager; @@ -1241,27 +1216,6 @@ private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) } } } -#else - private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) - { - var sceneManager = m_NetworkManager.SceneManager; - // Write the number of scene handles - writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count); - foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene) - { - // Write the scene handle - writer.WriteValueSafe(sceneHandleObjects.Key); - // Write the number of NetworkObjectIds to expect - writer.WriteValueSafe(sceneHandleObjects.Value.Count); - foreach (var networkObject in sceneHandleObjects.Value) - { - writer.WriteValueSafe(networkObject.NetworkObjectId); - } - } - // Once we are done, clear the table - sceneManager.ObjectsMigratedIntoNewScene.Clear(); - } -#endif /// /// Deserialize scene handles and associated NetworkObjects that need to @@ -1271,16 +1225,12 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; var spawnManager = m_NetworkManager.SpawnManager; -#if !NGO_DAMODE - // Just always assure this has no entries - sceneManager.ObjectsMigratedIntoNewScene.Clear(); -#endif + var numberOfScenes = 0; var sceneHandle = 0; var objectCount = 0; var networkObjectId = (ulong)0; -#if NGO_DAMODE var ownerID = (ulong)0; reader.ReadValueSafe(out ownerID); m_OwnerId = ownerID; @@ -1313,27 +1263,6 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) sceneManager.ObjectsMigratedIntoNewScene[sceneHandle][ownerID].Add(networkObject); } } -#else - reader.ReadValueSafe(out numberOfScenes); - for (int i = 0; i < numberOfScenes; i++) - { - reader.ReadValueSafe(out sceneHandle); - sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, new List()); - reader.ReadValueSafe(out objectCount); - for (int j = 0; j < objectCount; j++) - { - reader.ReadValueSafe(out networkObjectId); - if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId)) - { - NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!"); - continue; - } - // Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed - // - sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(spawnManager.SpawnedObjects[networkObjectId]); - } - } -#endif } @@ -1343,7 +1272,6 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) /// has completed synchronization to assure the associated NetworkObjects being /// migrated to a new scene are instantiated and spawned. /// -#if NGO_DAMODE private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; @@ -1416,82 +1344,8 @@ internal void ProcessDeferredObjectSceneChangedEvents() } objectsMovedEvent.ObjectsMigratedTable.Clear(); } - - sceneManager.DeferredObjectsMovedEvents.Clear(); - } -#else - private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) - { - var sceneManager = m_NetworkManager.SceneManager; - var spawnManager = m_NetworkManager.SpawnManager; - var numberOfScenes = 0; - var sceneHandle = 0; - var objectCount = 0; - var networkObjectId = (ulong)0; - - var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent() - { - ObjectsMigratedTable = new Dictionary>() - }; - - reader.ReadValueSafe(out numberOfScenes); - for (int i = 0; i < numberOfScenes; i++) - { - reader.ReadValueSafe(out sceneHandle); - deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List()); - reader.ReadValueSafe(out objectCount); - for (int j = 0; j < objectCount; j++) - { - reader.ReadValueSafe(out networkObjectId); - deferredObjectsMovedEvent.ObjectsMigratedTable[sceneHandle].Add(networkObjectId); - } - } - sceneManager.DeferredObjectsMovedEvents.Add(deferredObjectsMovedEvent); - } - - internal void ProcessDeferredObjectSceneChangedEvents() - { - var sceneManager = m_NetworkManager.SceneManager; - var spawnManager = m_NetworkManager.SpawnManager; - if (sceneManager.DeferredObjectsMovedEvents.Count == 0) - { - return; - } - foreach (var objectsMovedEvent in sceneManager.DeferredObjectsMovedEvents) - { - foreach (var keyEntry in objectsMovedEvent.ObjectsMigratedTable) - { - if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(keyEntry.Key)) - { - sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, new List()); - } - foreach (var objectId in keyEntry.Value) - { - if (!spawnManager.SpawnedObjects.ContainsKey(objectId)) - { - NetworkLog.LogWarning($"[Deferred][Object Scene Migration] Trying to synchronize NetworkObjectId ({objectId}) but it was not spawned or no longer exists!"); - continue; - } - var networkObject = spawnManager.SpawnedObjects[objectId]; - if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Contains(networkObject)) - { - sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(networkObject); - } - } - } - objectsMovedEvent.ObjectsMigratedTable.Clear(); - } - sceneManager.DeferredObjectsMovedEvents.Clear(); - - // If there are any pending objects to migrate, then migrate them - if (sceneManager.ObjectsMigratedIntoNewScene.Count > 0) - { - sceneManager.MigrateNetworkObjectsIntoScenes(); - } } -#endif - /// /// Used to release the pooled network buffer diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index 4c6b9e3f92..734d9885ee 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -55,12 +55,10 @@ public enum SceneEventProgressStatus /// This is returned when a client attempts to perform a server only action /// ServerOnlyAction, -#if NGO_DAMODE /// /// This is returned when a client that is not the session owner attempts to perform a session owner only action /// SessionOwnerOnlyAction, -#endif } /// @@ -163,25 +161,15 @@ internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressSta if (status == SceneEventProgressStatus.Started) { m_NetworkManager = networkManager; -#if NGO_DAMODE WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut; if ((networkManager.IsServer && !networkManager.DistributedAuthorityMode) || (networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner)) { -#else - if (networkManager.IsServer) - { - WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut; -#endif m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; // Track the clients that were connected when we started this event foreach (var connectedClientId in networkManager.ConnectedClientsIds) { -#if NGO_DAMODE // Ignore the host or session owner if ((!networkManager.DistributedAuthorityMode && NetworkManager.ServerClientId == connectedClientId) || (networkManager.DistributedAuthorityMode && networkManager.CurrentSessionOwner == connectedClientId)) -#else - if (NetworkManager.ServerClientId == connectedClientId) -#endif { continue; } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 9eb34ceb68..3829d498ea 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -43,7 +43,6 @@ public class NetworkSpawnManager /// private Dictionary m_ObjectToOwnershipTable = new Dictionary(); -#if NGO_DAMODE /// /// In distributed authority mode, a list of known spawned player NetworkObject instance is maintained by each client. /// @@ -142,7 +141,6 @@ private void RemovePlayerObject(NetworkObject playerObject, bool keepObservers = player.Observers.Remove(playerObject.OwnerClientId); } } -#endif internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId) { @@ -151,7 +149,6 @@ internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId ObjectsToShowToClient.Add(clientId, new List()); } ObjectsToShowToClient[clientId].Add(networkObject); -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { if (!ClientsToShowObject.ContainsKey(networkObject)) @@ -160,13 +157,11 @@ internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId } ClientsToShowObject[networkObject].Add(clientId); } -#endif } // returns whether any matching objects would have become visible and were returned to hidden state internal bool RemoveObjectFromShowingTo(NetworkObject networkObject, ulong clientId) { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { if (ClientsToShowObject.ContainsKey(networkObject)) @@ -178,7 +173,6 @@ internal bool RemoveObjectFromShowingTo(NetworkObject networkObject, ulong clien } } } -#endif var ret = false; if (!ObjectsToShowToClient.ContainsKey(clientId)) { @@ -222,13 +216,11 @@ internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, } else { -#if NGO_DAMODE // If we already had this owner in our table then just exit if (NetworkManager.DistributedAuthorityMode && previousOwner == newOwner) { return; } -#endif m_ObjectToOwnershipTable[networkObject.NetworkObjectId] = newOwner; } } @@ -316,12 +308,8 @@ internal ulong GetNetworkObjectId() m_NetworkObjectIdCounter++; -#if NGO_DAMODE // DANGO-TODO: Need a more robust solution here. return m_NetworkObjectIdCounter + (NetworkManager.LocalClientId * 10000); -#else - return m_NetworkObjectIdCounter; -#endif } /// @@ -333,7 +321,6 @@ public NetworkObject GetLocalPlayerObject() return GetPlayerNetworkObject(NetworkManager.LocalClientId); } -#if NGO_DAMODE /// /// Returns all instances assigned to the client identifier /// @@ -347,7 +334,7 @@ public List GetPlayerNetworkObjects(ulong clientId) } return null; } -#endif + /// /// Returns the player object with a given clientId or null if one does not exist. This is only valid server side. /// @@ -355,7 +342,6 @@ public List GetPlayerNetworkObjects(ulong clientId) /// The player object with a given clientId or null if one does not exist public NetworkObject GetPlayerNetworkObject(ulong clientId) { -#if NGO_DAMODE if (!NetworkManager.DistributedAuthorityMode) { if (!NetworkManager.IsServer && NetworkManager.LocalClientId != clientId) @@ -374,16 +360,7 @@ public NetworkObject GetPlayerNetworkObject(ulong clientId) return m_PlayerObjectsTable[clientId].First(); } } -#else - if (!NetworkManager.IsServer && NetworkManager.LocalClientId != clientId) - { - throw new NotServerException("Only the server can find player objects from other clients."); - } - if (TryGetNetworkClient(clientId, out NetworkClient networkClient)) - { - return networkClient.PlayerObject; - } -#endif + return null; } @@ -412,15 +389,13 @@ private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient return false; } -#if NGO_DAMODE protected virtual void InternalOnOwnershipChanged(ulong perviousOwner, ulong newOwner) { } -#endif + internal void RemoveOwnership(NetworkObject networkObject) { -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode && !NetworkManager.ShutdownInProgress) { if (networkObject.IsOwnershipDistributable || networkObject.IsOwnershipTransferable) @@ -446,21 +421,8 @@ internal void RemoveOwnership(NetworkObject networkObject) } } ChangeOwnership(networkObject, NetworkManager.ServerClientId, true); -#else - ChangeOwnership(networkObject, NetworkManager.ServerClientId); -#endif - } - -#if !NGO_DAMODE - internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) - { - if (!NetworkManager.IsServer) - { - throw new NotServerException("Only the server can change ownership"); - } -#else internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool isAuthorized, bool isRequestApproval = false) { if (NetworkManager.DistributedAuthorityMode) @@ -510,13 +472,13 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { throw new NotServerException("Only the server can change ownership"); } -#endif + if (!networkObject.IsSpawned) { throw new SpawnStateException("Object is not spawned"); } -#if NGO_DAMODE + if (networkObject.OwnerClientId == clientId && networkObject.PreviousOwnerId == clientId) { if (NetworkManager.LogLevel == LogLevel.Developer) @@ -528,9 +490,6 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool // Used to distinguish whether a new owner should receive any currently dirty NetworkVariable updates networkObject.PreviousOwnerId = networkObject.OwnerClientId; -#else - var previous = networkObject.OwnerClientId; -#endif // Assign the new owner networkObject.OwnerClientId = clientId; @@ -547,7 +506,6 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool // Always notify locally on the server when a new owner is assigned networkObject.InvokeBehaviourOnGainedOwnership(); -#if NGO_DAMODE var size = 0; if (NetworkManager.DistributedAuthorityMode) { @@ -605,30 +563,12 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool } } } -#else - var message = new ChangeOwnershipMessage - { - NetworkObjectId = networkObject.NetworkObjectId, - OwnerClientId = networkObject.OwnerClientId, - }; - foreach (var client in NetworkManager.ConnectedClients) - { - if (networkObject.IsNetworkVisibleTo(client.Value.ClientId)) - { - var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId); - NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); - } - } -#endif + // After we have sent the change ownership message to all client observers, invoke the ownership changed notification. /// !!Important!! /// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership /// change can be sent from NetworkBehaviours that override the -#if NGO_DAMODE networkObject.InvokeOwnershipChanged(networkObject.PreviousOwnerId, clientId); -#else - networkObject.InvokeOwnershipChanged(previous, clientId); -#endif } internal bool HasPrefab(NetworkObject.SceneObject sceneObject) @@ -703,13 +643,9 @@ public NetworkObject InstantiateAndSpawn(NetworkObject networkPrefab, ulong owne return null; } -#if NGO_DAMODE ownerClientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; // We only need to check for authority when running in client-server mode if (!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) -#else - if (!NetworkManager.IsServer) -#endif { Debug.LogError(InstantiateAndSpawnErrors[InstantiateAndSpawnErrorTypes.NotAuthority]); return null; @@ -738,12 +674,8 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ { var networkObject = networkPrefab; // Host spawns the ovveride and server spawns the original prefab unless forceOverride is set to true where both server or host will spawn the override. -#if NGO_DAMODE // In distributed authority mode, we alaways get the override if (forceOverride || NetworkManager.IsHost || NetworkManager.DistributedAuthorityMode) -#else - if (forceOverride || NetworkManager.IsHost) -#endif { networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation); } @@ -875,10 +807,8 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO { networkObject.DestroyWithScene = sceneObject.DestroyWithScene; networkObject.NetworkSceneHandle = sceneObject.NetworkSceneHandle; -#if NGO_DAMODE networkObject.DontDestroyWithOwner = sceneObject.DontDestroyWithOwner; networkObject.Ownership = (NetworkObject.OwnershipStatus)sceneObject.OwnershipFlags; -#endif // SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject) // This is a special case scenario where a late joining client has joined and loaded one or @@ -975,12 +905,8 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo if (networkObject.IsSpawned) { -#if NGO_DAMODE Debug.LogError($"{networkObject.name} is already spawned!"); return; -#else - throw new SpawnStateException("Object is already spawned"); -#endif } if (!sceneObject) @@ -992,7 +918,6 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo } } -#if NGO_DAMODE // DANGO-TODO: It would be nice to allow users to specify which clients are observers prior to spawning // For now, this is the best place I could find to add all connected clients as observers for newly // instantiated and spawned NetworkObjects on the authoritative side. @@ -1031,7 +956,6 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo } } } -#endif SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene); } @@ -1086,7 +1010,7 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkObject.IsPlayerObject = playerObject; networkObject.OwnerClientId = ownerClientId; -#if NGO_DAMODE + // When spawned, previous owner is always the first assigned owner networkObject.PreviousOwnerId = ownerClientId; @@ -1095,47 +1019,12 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong { networkObject.SetOwnershipLock(); } -#endif SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject); SpawnedObjectsList.Add(networkObject); -#if !NGO_DAMODE - // TODO: This whole segement can be deleted when we remove NGO_DAMODE - if (NetworkManager.IsServer) - { - if (playerObject) - { - // If there was an already existing player object for this player, then mark it as no longer - // a player object. - if (NetworkManager.ConnectedClients[ownerClientId].PlayerObject != null) - { - NetworkManager.ConnectedClients[ownerClientId].PlayerObject.IsPlayerObject = false; - } - NetworkManager.ConnectedClients[ownerClientId].PlayerObject = networkObject; - } - } - else if (ownerClientId == NetworkManager.LocalClientId) - { - if (playerObject) - { - // If there was an already existing player object for this player, then mark it as no longer a player object. - if (NetworkManager.LocalClient.PlayerObject != null) - { - NetworkManager.LocalClient.PlayerObject.IsPlayerObject = false; - } - NetworkManager.LocalClient.PlayerObject = networkObject; - } - } -#endif - -#if NGO_DAMODE // If we are not running in DA mode, this is the server, and the NetworkObject has SpawnWithObservers set, // then add all connected clients as observers if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkObject.SpawnWithObservers) -#else - // If we are the server and should spawn with observers - if (NetworkManager.IsServer && networkObject.SpawnWithObservers) -#endif { // Add client observers for (int i = 0; i < NetworkManager.ConnectedClientsIds.Count; i++) @@ -1173,12 +1062,11 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong { networkObject.SubscribeToActiveSceneForSynch(); } -#if NGO_DAMODE + if (networkObject.IsPlayerObject) { UpdateNetworkClientPlayer(networkObject); } -#endif // If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set // then assign this to the PrefabGlobalObjectIdHash @@ -1191,14 +1079,10 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject) { // If we are a host and sending to the host's client id, then we can skip sending ourselves the spawn message. -#if NGO_DAMODE var updateObservers = NetworkManager.DistributedAuthorityMode && networkObject.SpawnWithObservers; // Only skip if distributed authority mode is not enabled if (clientId == NetworkManager.ServerClientId && !NetworkManager.DistributedAuthorityMode) -#else - if (clientId == NetworkManager.ServerClientId) -#endif { return; } @@ -1206,18 +1090,14 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject var message = new CreateObjectMessage { ObjectInfo = networkObject.GetMessageSceneObject(clientId), -#if NGO_DAMODE IncludesSerializedObject = true, UpdateObservers = NetworkManager.DistributedAuthorityMode, ObserverIds = NetworkManager.DistributedAuthorityMode ? networkObject.Observers.ToArray() : null, -#endif }; var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId); NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); } -#if NGO_DAMODE - /// /// Only used to update object visibility/observers. /// ** Clients are the only instances that use this method ** @@ -1245,7 +1125,6 @@ internal void SendSpawnCallForObserverUpdate(ulong[] newObservers, NetworkObject NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); } } -#endif internal ulong? GetSpawnParentId(NetworkObject networkObject) { @@ -1264,11 +1143,7 @@ internal void SendSpawnCallForObserverUpdate(ulong[] newObservers, NetworkObject return parentNetworkObject.NetworkObjectId; } -#if NGO_DAMODE internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false, bool playerDisconnect = false) -#else - internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false) -#endif { if (!networkObject.IsSpawned) { @@ -1276,17 +1151,12 @@ internal void DespawnObject(NetworkObject networkObject, bool destroyObject = fa return; } -#if NGO_DAMODE if (!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) -#else - if (!NetworkManager.IsServer) -#endif { NetworkLog.LogErrorServer("Only server can despawn objects"); return; } -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode && networkObject.OwnerClientId != NetworkManager.LocalClientId) { if (!NetworkManager.DAHost || NetworkManager.DAHost && !playerDisconnect) @@ -1296,9 +1166,6 @@ internal void DespawnObject(NetworkObject networkObject, bool destroyObject = fa } } OnDespawnObject(networkObject, destroyObject, playerDisconnect); -#else - OnDespawnObject(networkObject, destroyObject); -#endif } // Makes scene objects ready to be reused @@ -1449,23 +1316,17 @@ internal void ServerSpawnSceneObjectsOnStartSweep() if (networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject.HasValue && networkObjects[i].IsSceneObject.Value)) { var ownerId = networkObjects[i].OwnerClientId; -#if NGO_DAMODE if (NetworkManager.DistributedAuthorityMode) { ownerId = NetworkManager.LocalClientId; } -#endif SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true); } } } } -#if NGO_DAMODE internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false) -#else - internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject) -#endif { if (NetworkManager == null) { @@ -1491,12 +1352,8 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec // If we are shutting down the NetworkManager, then ignore resetting the parent // and only attempt to remove the child's parent on the server-side -#if NGO_DAMODE var distributedAuthority = NetworkManager.DistributedAuthorityMode; if (!NetworkManager.ShutdownInProgress && (NetworkManager.IsServer || distributedAuthority)) -#else - if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer) -#endif { // Get all child NetworkObjects var objectsToRemoveParent = networkObject.GetComponentsInChildren(); @@ -1504,12 +1361,10 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec // Move child NetworkObjects to the root when parent NetworkObject is destroyed foreach (var spawnedNetObj in objectsToRemoveParent) { -#if NGO_DAMODE if (spawnedNetObj == networkObject) { continue; } -#endif var latestParent = spawnedNetObj.GetNetworkParenting(); // Only deparent the first generation children of the NetworkObject being spawned. // Ignore any nested children under first generation children. @@ -1517,12 +1372,11 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec { continue; } -#if NGO_DAMODE // 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; -#endif + // Try to remove the parent using the cached WorldPositionStays value // Note: WorldPositionStays will still default to true if this was an // in-scene placed NetworkObject and parenting was predefined in the @@ -1543,7 +1397,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } networkObject.InvokeBehaviourNetworkDespawn(); -#if NGO_DAMODE + if (NetworkManager != null && ((NetworkManager.IsServer && (!distributedAuthority || (distributedAuthority && modeDestroy))) || (distributedAuthority && networkObject.OwnerClientId == NetworkManager.LocalClientId))) @@ -1605,48 +1459,6 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } } } -#else - if (NetworkManager != null && NetworkManager.IsServer) - { - if (NetworkManager.NetworkConfig.RecycleNetworkIds) - { - ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId() - { - NetworkId = networkObject.NetworkObjectId, - ReleaseTime = NetworkManager.RealTimeProvider.UnscaledTime - }); - } - if (networkObject != null) - { - // As long as we have any remaining clients, then notify of the object being destroy. - if (NetworkManager.ConnectedClientsList.Count > 0) - { - m_TargetClientIds.Clear(); - - // 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 (networkObject.IsNetworkVisibleTo(clientId)) - { - m_TargetClientIds.Add(clientId); - } - } - - var message = new DestroyObjectMessage - { - NetworkObjectId = networkObject.NetworkObjectId, - DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true - }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds); - foreach (var targetClientId in m_TargetClientIds) - { - NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size); - } - } - } - } -#endif networkObject.IsSpawned = false; @@ -1655,7 +1467,6 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec SpawnedObjectsList.Remove(networkObject); } -#if NGO_DAMODE // DANGO-TODO: When we fix the issue with observers not being applied to NetworkObjects, // (client connect/disconnect) we can remove this hacky way of doing this. // Basically, when a player disconnects and/or is destroyed they are removed as an observer from all other client @@ -1674,7 +1485,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec { RemovePlayerObject(networkObject); } -#endif + // Always clear out the observers list when despawned networkObject.Observers.Clear(); @@ -1733,7 +1544,6 @@ internal void UpdateObservedNetworkObjects(ulong clientId) /// internal void HandleNetworkObjectShow() { -#if NGO_DAMODE // In distributed authority mode, we send a single message that is broadcasted to all clients // that will be shown the object (i.e. 1 message to service that then broadcasts that to the // targeted clients). When using a DAHost, we skip this and send like we do in client-server @@ -1747,7 +1557,7 @@ internal void HandleNetworkObjectShow() ObjectsToShowToClient.Clear(); return; } -#endif + // Handle NetworkObjects to show foreach (var client in ObjectsToShowToClient) { @@ -1763,9 +1573,6 @@ internal void HandleNetworkObjectShow() internal NetworkSpawnManager(NetworkManager networkManager) { NetworkManager = networkManager; -#if !NGO_DAMODE - } -#else } /// @@ -2026,6 +1833,5 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) DeferredDespawnObjects.Remove(deferredObjectEntry); } } -#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs b/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs index 2b1bc64501..e6a32afa29 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/IRealTimeProvider.cs @@ -6,8 +6,6 @@ internal interface IRealTimeProvider float UnscaledTime { get; } float UnscaledDeltaTime { get; } float DeltaTime { get; } -#if NGO_DAMODE float FixedDeltaTime { get; } -#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs b/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs index fa56d3fb8c..51a2044f7b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/RealTimeProvider.cs @@ -8,8 +8,6 @@ internal class RealTimeProvider : IRealTimeProvider public float UnscaledTime => Time.unscaledTime; public float UnscaledDeltaTime => Time.unscaledDeltaTime; public float DeltaTime => Time.deltaTime; -#if NGO_DAMODE public float FixedDeltaTime => Time.fixedDeltaTime; -#endif } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index d7f3dfe5f5..b414bcd58c 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -23,9 +23,7 @@ internal struct SceneEntry public Scene Scene; } -#if NGO_DAMODE public bool IsIntegrationTest() { return true; } -#endif internal static Dictionary>> SceneNameToSceneHandles = new Dictionary>>(); diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs index 02568e4e34..54f546bf33 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs @@ -87,9 +87,7 @@ protected Vector3 GetRandomVector3(float min, float max) return new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(min, max)); } -#if NGO_DAMODE public IntegrationTestWithApproximation(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif public IntegrationTestWithApproximation(HostOrServer hostOrServer) : base(hostOrServer) { } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs index 9fc51a0d6a..46ac3db8a1 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs @@ -6,11 +6,9 @@ public class MockTimeProvider : IRealTimeProvider public float UnscaledTime => (float)s_DoubleRealTime; public float UnscaledDeltaTime => (float)s_DoubleDelta; public float DeltaTime => (float)s_DoubleDelta; - -#if NGO_DAMODE - //DANGO-TODO: Figure out how we want to handle time travel with fixed delta time. + + // DANGO-EXP TODO: Figure out how we want to handle time travel with fixed delta time. public float FixedDeltaTime => (float)s_DoubleDelta; -#endif public static float StaticRealTimeSinceStartup => (float)s_DoubleRealTime; public static float StaticUnscaledTime => (float)s_DoubleRealTime; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 611fcce8a2..25dd190165 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -121,9 +121,7 @@ public enum HostOrServer { Host, Server, -#if NGO_DAMODE DAHost -#endif } protected GameObject m_PlayerPrefab; @@ -140,7 +138,6 @@ public enum HostOrServer protected Dictionary> m_PlayerNetworkObjects = new Dictionary>(); protected bool m_UseHost = true; -#if NGO_DAMODE protected bool m_DistributedAuthority; protected SessionModeTypes m_SessionModeType = SessionModeTypes.ClientServer; @@ -161,7 +158,6 @@ protected void SetDistributedAuthorityProperties(NetworkManager networkManager) networkManager.NetworkConfig.UseCMBService = UseCMBService() && m_DistributedAuthority; } -#endif protected int m_TargetFrameRate = 60; private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode; @@ -490,9 +486,7 @@ protected IEnumerator CreateAndStartNewClient() { var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel); networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; -#if NGO_DAMODE SetDistributedAuthorityProperties(networkManager); -#endif // Notification that the new client (NetworkManager) has been created // in the event any modifications need to be made before starting the client @@ -523,19 +517,16 @@ protected IEnumerator CreateAndStartNewClient() AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!\n {m_InternalErrorLog}"); ClientNetworkManagerPostStart(networkManager); -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { yield return WaitForConditionOrTimeOut(() => AllPlayerObjectClonesSpawned(networkManager)); AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{networkManager.LocalClientId}'s player object!"); } -#endif VerboseDebug($"[{networkManager.name}] Created and connected!"); } } -#if NGO_DAMODE private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient) { m_InternalErrorLog.Clear(); @@ -581,7 +572,6 @@ private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient) } return true; } -#endif /// /// This will create, start, and connect a new client while in the middle of an @@ -591,9 +581,7 @@ protected void CreateAndStartNewClientWithTimeTravel() { var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel); networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; -#if NGO_DAMODE SetDistributedAuthorityProperties(networkManager); -#endif // Notification that the new client (NetworkManager) has been created // in the event any modifications need to be made before starting the client @@ -681,16 +669,12 @@ protected void CreateServerAndClients(int numberOfClients) // Set the player prefab for the server and clients m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; -#if NGO_DAMODE SetDistributedAuthorityProperties(m_ServerNetworkManager); -#endif foreach (var client in m_ClientNetworkManagers) { client.NetworkConfig.PlayerPrefab = m_PlayerPrefab; -#if NGO_DAMODE SetDistributedAuthorityProperties(client); -#endif } // Provides opportunity to allow child derived classes to @@ -838,13 +822,9 @@ protected IEnumerator StartServerAndClients() // Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server // is started and after each client is started. -#if NGO_DAMODE // When using the CMBService, don't start the server. bool startServer = !(UseCMBService() && m_DistributedAuthority); if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, startServer, m_ServerNetworkManager, m_ClientNetworkManagers)) -#else - if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers)) -#endif { Debug.LogError("Failed to start instances"); Assert.Fail("Failed to start instances"); @@ -855,9 +835,7 @@ protected IEnumerator StartServerAndClients() // to load their own scenes. if (m_ServerNetworkManager.NetworkConfig.EnableSceneManagement) { -#if NGO_DAMODE if (startServer) -#endif { var scenesLoaded = m_ServerNetworkManager.SceneManager.ScenesLoaded; m_ServerNetworkManager.SceneManager.SceneManagerHandler.PopulateLoadedScenes(ref scenesLoaded, m_ServerNetworkManager); @@ -901,7 +879,6 @@ protected IEnumerator StartServerAndClients() m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject); } } -#if NGO_DAMODE if (m_DistributedAuthority) { //yield return WaitForConditionOrTimeOut(AllClientPlayersSpawned); @@ -920,7 +897,6 @@ protected IEnumerator StartServerAndClients() AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{m_ServerNetworkManager.LocalClientId}'s player object!\n {m_InternalErrorLog}"); } } -#endif ClientNetworkManagerPostStartInit(); // Notification that at this time the server and client(s) are instantiated, // started, and connected on both sides. @@ -943,13 +919,9 @@ protected void StartServerAndClientsWithTimeTravel() // Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server // is started and after each client is started. -#if NGO_DAMODE // When using the CMBService, don't start the server. var usingCMBService = UseCMBService() && m_DistributedAuthority; if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, !usingCMBService, m_ServerNetworkManager, m_ClientNetworkManagers)) -#else - if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers)) -#endif { Debug.LogError("Failed to start instances"); Assert.Fail("Failed to start instances"); @@ -999,7 +971,6 @@ protected void StartServerAndClientsWithTimeTravel() } } -#if NGO_DAMODE if (m_DistributedAuthority) { @@ -1017,7 +988,6 @@ protected void StartServerAndClientsWithTimeTravel() AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{m_ServerNetworkManager.LocalClientId}'s player object!"); } } -#endif ClientNetworkManagerPostStartInit(); @@ -1596,11 +1566,9 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) Assert.IsNotNull(m_ServerNetworkManager, prefabCreateAssertError); Assert.IsFalse(m_ServerNetworkManager.IsListening, prefabCreateAssertError); var prefabObject = NetcodeIntegrationTestHelpers.CreateNetworkObjectPrefab(baseName, m_ServerNetworkManager, m_ClientNetworkManagers); -#if NGO_DAMODE // DANGO-TODO: Ownership flags could require us to change this // For testing purposes, we default to true for the distribute ownership property when in distirbuted authority session mode. prefabObject.GetComponent().Ownership |= NetworkObject.OwnershipStatus.Distributable; -#endif return prefabObject; } @@ -1637,7 +1605,6 @@ private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager var newInstance = Object.Instantiate(prefabNetworkObject.gameObject); var networkObjectToSpawn = newInstance.GetComponent(); -#if NGO_DAMODE if (owner.NetworkConfig.SessionMode == SessionModeTypes.DistributedAuthority) { networkObjectToSpawn.NetworkManagerOwner = owner; // Required to assure the client does the spawning @@ -1683,26 +1650,6 @@ private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager } } } -#else - networkObjectToSpawn.NetworkManagerOwner = m_ServerNetworkManager; // Required to assure the server does the spawning - if (owner == m_ServerNetworkManager) - { - if (m_UseHost) - { - networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); - } - else - { - networkObjectToSpawn.Spawn(destroyWithScene); - } - } - else - { - networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); - } -#endif - - return newInstance; } @@ -1740,21 +1687,16 @@ private List SpawnObjects(NetworkObject prefabNetworkObject, Network /// public NetcodeIntegrationTest() { -#if NGO_DAMODE m_SessionModeType = OnGetSessionmode(); m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; NetworkMessageManager.EnableMessageOrderConsoleLog = false; -#endif - } -#if NGO_DAMODE public NetcodeIntegrationTest(SessionModeTypes sessionMode) { m_SessionModeType = sessionMode; m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; } -#endif /// /// Optional Host or Server integration tests @@ -1774,13 +1716,9 @@ public NetcodeIntegrationTest(SessionModeTypes sessionMode) /// public NetcodeIntegrationTest(HostOrServer hostOrServer) { -#if NGO_DAMODE m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost; m_SessionModeType = hostOrServer == HostOrServer.DAHost ? SessionModeTypes.DistributedAuthority : SessionModeTypes.ClientServer; m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; -#else - m_UseHost = hostOrServer == HostOrServer.Host; -#endif } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 1219f53518..564bde6fdb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -85,19 +85,11 @@ public int DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType return 0; } -#if NGO_DAMODE public override void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType) -#else - public override void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context) -#endif { OnBeforeDefer?.Invoke(this, key); DeferMessageCalled = true; -#if NGO_DAMODE base.DeferMessage(trigger, key, reader, ref context, messageType); -#else - base.DeferMessage(trigger, key, reader, ref context); -#endif } public override void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key) @@ -211,10 +203,8 @@ public class DeferredMessagingTest : NetcodeIntegrationTest protected override void OnInlineSetup() { -#if NGO_DAMODE // Revert back to standard deferred message format for tests (for now) DeferredMessageManager.IncludeMessageType = false; -#endif DeferredMessageTestRpcAndNetworkVariableComponent.ClientInstances.Clear(); DeferredMessageTestRpcComponent.ClientInstances.Clear(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs index 7a777a7d46..b6abd74b0f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE using System; using System.Collections; using System.Collections.Generic; @@ -265,4 +264,3 @@ private void Log(string message) } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs index 1f0e3f62de..23b34906c7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE using System; using System.Collections; using System.Collections.Generic; @@ -519,4 +518,3 @@ protected bool Approximately(Quaternion a, Quaternion b) } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 14b5d60868..58dd33b9e9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE using System; using System.Collections; using System.Collections.Generic; @@ -717,4 +716,3 @@ public IEnumerator WaitForMessageReceived(T _, float timeout = 5) where T : I } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index 4dc0f26bd6..fa7a641390 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE using System.Collections; using System.Collections.Generic; using System.Linq; @@ -297,4 +296,3 @@ public IEnumerator ValidatePlayerObjects() } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs index b5e9dd7f92..e56798bc86 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -1,4 +1,3 @@ -#if NGO_DAMODE using System; using System.Collections; using System.Collections.Generic; @@ -405,4 +404,3 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) } } } -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs index 9ce08c68ea..45f7081667 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs @@ -46,10 +46,8 @@ public void Changed(NetworkListEvent listEvent) } } -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class NetworkListChangedTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -59,9 +57,7 @@ public class NetworkListChangedTests : NetcodeIntegrationTest private NetworkObject m_NetSpawnedObject1; -#if NGO_DAMODE public NetworkListChangedTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override void OnServerAndClientsCreated() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs index ace85293b6..9cf021a5c7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs @@ -88,11 +88,7 @@ public void SetOwnerWrite() public override void OnNetworkSpawn() { // Non-Authority will register each NetworkObject when it is spawned -#if NGO_DAMODE if ((NetworkManager.DistributedAuthorityMode && !IsOwner) || (!NetworkManager.DistributedAuthorityMode && !IsServer)) -#else - if (!IsServer) -#endif { NetworkBehaviourUpdaterTests.ClientSideNotifyObjectSpawned(gameObject); } @@ -104,11 +100,7 @@ public override void OnNetworkSpawn() /// public void SetNetworkVariableValues() { -#if NGO_DAMODE if ((NetworkManager.DistributedAuthorityMode && IsOwner) || (!NetworkManager.DistributedAuthorityMode && IsServer)) -#else - if (IsServer) -#endif { switch (NumberOfNetVarsToCheck) { @@ -144,14 +136,12 @@ public struct NetVarCombinationTypes /// Server and Distributed Authority modes require at least 1 client while the host does not. /// /// [Session Mode][Number of Clients][First NetVar Type][Second NetVar Type] -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] [TestFixture(HostOrServer.DAHost, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] [TestFixture(HostOrServer.DAHost, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] [TestFixture(HostOrServer.DAHost, 2, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] -#endif [TestFixture(HostOrServer.Server, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] [TestFixture(HostOrServer.Server, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] [TestFixture(HostOrServer.Server, 1, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] @@ -212,21 +202,19 @@ protected override void OnServerAndClientsCreated() // GameObject of this prefab var netVarContainer = m_PrefabToSpawn.AddComponent(); netVarContainer.NumberOfNetVarsToCheck = m_NetVarCombinationTypes.FirstType; -#if NGO_DAMODE if (m_SessionModeType == SessionModeTypes.DistributedAuthority) { netVarContainer.SetOwnerWrite(); } -#endif + netVarContainer.ValueToSetNetVarTo = NetVarValueToSet; netVarContainer = m_PrefabToSpawn.AddComponent(); -#if NGO_DAMODE if (m_SessionModeType == SessionModeTypes.DistributedAuthority) { netVarContainer.SetOwnerWrite(); } -#endif + netVarContainer.NumberOfNetVarsToCheck = m_NetVarCombinationTypes.SecondType; netVarContainer.ValueToSetNetVarTo = NetVarValueToSet; @@ -255,11 +243,8 @@ public IEnumerator BehaviourUpdaterAllTests([Values(1, 2)] int numToSpawn) // the appropriate number of NetworkObjects with the NetVarContainer behaviour var numberOfObjectsToSpawn = numToSpawn * NumberOfClients; -#if NGO_DAMODE var authority = m_SessionModeType == SessionModeTypes.DistributedAuthority ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; -#else - var authority = m_ServerNetworkManager; -#endif + // spawn the objects for (int i = 0; i < numToSpawn; i++) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index cde1829f02..7a751b3cd8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -14,17 +14,13 @@ namespace Unity.Netcode.RuntimeTests /// - Client destroy spawned => throw exception. /// -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class NetworkObjectDestroyTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; -#if NGO_DAMODE public NetworkObjectDestroyTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif /// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. @@ -45,7 +41,6 @@ public IEnumerator TestNetworkObjectAuthorityDestroy() Assert.IsNotNull(clientClientPlayerResult.Result.gameObject); var targetNetworkManager = m_ClientNetworkManagers[0]; -#if NGO_DAMODE if (m_DistributedAuthority) { targetNetworkManager = m_ClientNetworkManagers[1]; @@ -53,7 +48,6 @@ public IEnumerator TestNetworkObjectAuthorityDestroy() Object.Destroy(clientClientPlayerResult.Result.gameObject); } else -#endif { // destroy the authoritative player (client-server) Object.Destroy(serverClientPlayerResult.Result.gameObject); @@ -95,14 +89,12 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c //destroying a NetworkObject while shutting down is allowed if (isShuttingDown) { -#if NGO_DAMODE if (m_DistributedAuthority) { // Shutdown the 2nd client m_ClientNetworkManagers[1].Shutdown(); } else -#endif { // Shutdown the m_ClientNetworkManagers[0].Shutdown(); @@ -116,7 +108,6 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c m_ClientPlayerName = clientPlayer.gameObject.name; m_ClientNetworkObjectId = clientPlayer.NetworkObjectId; -#if NGO_DAMODE if (m_DistributedAuthority) { m_ClientPlayerName = m_PlayerNetworkObjects[m_ClientNetworkManagers[1].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject.name; @@ -130,7 +121,6 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c Object.DestroyImmediate(m_PlayerNetworkObjects[m_ClientNetworkManagers[1].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject); } else -#endif { // the 1st client attempts to destroy its own player object (if shutting down then "ok" if not then not "ok") Object.DestroyImmediate(m_ClientNetworkManagers[0].LocalClient.PlayerObject.gameObject); @@ -146,7 +136,6 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c private bool HaveLogsBeenReceived() { -#if NGO_DAMODE if (m_DistributedAuthority) { if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-owner client is not valid during a distributed authority session. Call Destroy or Despawn on the client-owner instead.")) @@ -166,19 +155,6 @@ private bool HaveLogsBeenReceived() return false; } } -#else - { - if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] Destroy a spawned NetworkObject on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) - { - return false; - } - - if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode-Server Sender={m_ClientNetworkManagers[0].LocalClientId}] Destroy a spawned NetworkObject on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) - { - return false; - } - } -#endif return true; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs index 78a92c15aa..f77a06e786 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs @@ -8,9 +8,7 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkObjectDontDestroyWithOwnerTests : NetcodeIntegrationTest @@ -39,7 +37,6 @@ public IEnumerator DontDestroyWithOwnerTest() yield return WaitForConditionOrTimeOut(() => client.SpawnManager.GetClientOwnedObjects(clientId).Count() == k_NumberObjectsToSpawn + 1); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client to have 33 NetworkObjects spawned! Only {client.SpawnManager.GetClientOwnedObjects(clientId).Count()} were assigned!"); -#if NGO_DAMODE // Since clients spawn their objects locally in distributed authority mode, we have to rebuild the list of the client // owned objects on the (DAHost) server-side because when the client disconnects it will destroy its local instances. if (m_DistributedAuthority) @@ -55,7 +52,6 @@ public IEnumerator DontDestroyWithOwnerTest() } } } -#endif // disconnect the client that owns all the clients NetcodeIntegrationTestHelpers.StopOneClient(client); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs index 3a89801374..56adc0a686 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs @@ -10,10 +10,7 @@ namespace Unity.Netcode.RuntimeTests /// /// Tests that check OnNetworkDespawn being invoked /// - -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkObjectOnNetworkDespawnTests : NetcodeIntegrationTest @@ -95,12 +92,10 @@ public IEnumerator TestNetworkObjectDespawnOnShutdown([Values(InstanceTypes.Serv { var networkManager = despawnCheck == InstanceTypes.Server ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; var networkManagerOwner = m_ServerNetworkManager; -#if NGO_DAMODE if (m_DistributedAuthority) { networkManagerOwner = networkManager; } -#endif // Spawn the test object var spawnedObject = SpawnObject(m_ObjectToSpawn, networkManagerOwner); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 92a2dc7453..cefdc2dcba 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -7,10 +7,8 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class NetworkObjectOnSpawnTests : NetcodeIntegrationTest { private GameObject m_TestNetworkObjectPrefab; @@ -31,9 +29,7 @@ public enum ObserverTestTypes private const string k_WithObserversError = "Not all clients spawned the"; private const string k_WithoutObserversError = "A client spawned the"; -#if NGO_DAMODE public NetworkObjectOnSpawnTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override void OnServerAndClientsCreated() { @@ -109,14 +105,12 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp { if (sceneManagement == SceneManagementState.SceneManagementDisabled) { -#if NGO_DAMODE // When scene management is disabled, we need this wait period for all clients to be up to date with // all other clients before beginning the process of stopping all clients. if (m_DistributedAuthority) { yield return new WaitForSeconds(0.5f); } -#endif // Disable prefabs to prevent them from being destroyed foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs index bdf265e3ca..e198f0d118 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs @@ -8,10 +8,8 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class NetworkObjectOwnershipPropertiesTests : NetcodeIntegrationTest { private class DummyNetworkBehaviour : NetworkBehaviour @@ -32,9 +30,7 @@ private class DummyNetworkBehaviour : NetworkBehaviour private bool m_InitialOwnerOwnedBySever; private bool m_TargetOwnerOwnedBySever; -#if NGO_DAMODE public NetworkObjectOwnershipPropertiesTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override IEnumerator OnTearDown() { @@ -49,9 +45,7 @@ protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("ClientOwnedObject"); m_PrefabToSpawn.gameObject.AddComponent(); -#if NGO_DAMODE m_PrefabToSpawn.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); -#endif } public enum InstanceTypes @@ -65,7 +59,6 @@ private bool OwnershipPropagated() { var conditionMet = true; m_OwnershipPropagatedFailures.Clear(); -#if NGO_DAMODE // In distributed authority mode, we will check client owner to DAHost owner with InstanceTypes.Server and client owner to client // when InstanceTypes.Client if (m_DistributedAuthority) @@ -102,21 +95,6 @@ private bool OwnershipPropagated() m_OwnershipPropagatedFailures.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); } } -#else - if (m_NextTargetOwner != m_ServerNetworkManager) - { - if (!m_NextTargetOwner.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) - { - conditionMet = false; - m_OwnershipPropagatedFailures.AppendLine($"Client-{m_NextTargetOwner.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); - } - } - if (!m_ServerNetworkManager.SpawnManager.GetClientOwnedObjects(m_NextTargetOwner.LocalClientId).Any(x => x.NetworkObjectId == m_OwnerSpawnedInstance.NetworkObjectId)) - { - conditionMet = false; - m_OwnershipPropagatedFailures.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} has no ownership entry for {m_OwnerSpawnedInstance.name} ({m_OwnerSpawnedInstance.NetworkObjectId})"); - } -#endif return conditionMet; } @@ -147,7 +125,7 @@ public IEnumerator ValidatePropertiesWithOwnershipChanges([Values(InstanceTypes. { m_NextTargetOwner = instanceType == InstanceTypes.Server ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; m_InitialOwner = instanceType == InstanceTypes.Client ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; -#if NGO_DAMODE + // In distributed authority mode, we will check client owner to DAHost owner with InstanceTypes.Server and client owner to client // when InstanceTypes.Client if (m_DistributedAuthority) @@ -159,7 +137,7 @@ public IEnumerator ValidatePropertiesWithOwnershipChanges([Values(InstanceTypes. } m_PrefabToSpawn.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); } -#endif + m_InitialOwnerId = m_InitialOwner.LocalClientId; m_TargetOwnerId = m_NextTargetOwner.LocalClientId; m_InitialOwnerOwnedBySever = m_InitialOwner.IsServer; @@ -185,7 +163,6 @@ public IEnumerator ValidatePropertiesWithOwnershipChanges([Values(InstanceTypes. // The authority always changes the ownership // Client-Server: It will always be the host instance // Distributed Authority: It can be either the DAHost or the client -#if NGO_DAMODE if (m_DistributedAuthority) { // Use the target client's instance to change ownership @@ -208,11 +185,6 @@ public IEnumerator ValidatePropertiesWithOwnershipChanges([Values(InstanceTypes. // Provide enough time for the client to receive and process the change in ownership message. yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); } -#else - m_OwnerSpawnedInstance.ChangeOwnership(m_NextTargetOwner.LocalClientId); - // Provide enough time for the client to receive and process the change in ownership message. - yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); -#endif // Ensure it's the ownership tables are updated yield return WaitForConditionOrTimeOut(OwnershipPropagated); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index a64efaa770..77d5a939a1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -41,9 +41,8 @@ public void ResetFlags() } } -#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkObjectOwnershipTests : NetcodeIntegrationTest @@ -72,12 +71,10 @@ protected override void OnServerAndClientsCreated() { m_OwnershipPrefab = CreateNetworkObjectPrefab("OnwershipPrefab"); m_OwnershipPrefab.AddComponent(); -#if NGO_DAMODE if (m_DistributedAuthority) { m_OwnershipPrefab.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); } -#endif base.OnServerAndClientsCreated(); } @@ -169,7 +166,6 @@ public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChec else { // Validates that when ownership is removed the server gets an OnGainedOwnership notification -#if NGO_DAMODE // In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service) if (m_DistributedAuthority) { @@ -179,9 +175,6 @@ public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChec { serverObject.RemoveOwnership(); } -#else - serverObject.RemoveOwnership(); -#endif } yield return WaitForConditionOrTimeOut(() => serverComponent.OnGainedOwnershipFired && serverComponent.OwnerClientId == m_ServerNetworkManager.LocalClientId); @@ -326,7 +319,6 @@ bool WaitForClientsToSpawnNetworkObject() else { // Validates that when ownership is removed the server gets an OnGainedOwnership notification -#if NGO_DAMODE // In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service) if (m_DistributedAuthority) { @@ -336,9 +328,6 @@ bool WaitForClientsToSpawnNetworkObject() { serverObject.RemoveOwnership(); } -#else - serverObject.RemoveOwnership(); -#endif } yield return WaitForConditionOrTimeOut(ownershipMessageHooks); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs index e8a0472b35..7e415960bc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs @@ -6,10 +6,9 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE + [TestFixture(SessionModeTypes.ClientServer)] [TestFixture(SessionModeTypes.DistributedAuthority)] -#endif public class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; @@ -19,9 +18,7 @@ public class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest private NetworkPrefab m_PrefabToSpawn; -#if NGO_DAMODE public NetworkObjectSpawnManyObjectsTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif // Using this component assures we will know precisely how many prefabs were spawned on the client public class SpawnObjecTrackingComponent : NetworkBehaviour { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index 8cd6c9fd81..0a907c6e16 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -8,9 +8,8 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE + [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.DAHost)] -#endif [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Host)] [TestFixture(VariableLengthSafety.EnabledNetVarSafety, HostOrServer.Host)] [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Server)] @@ -42,12 +41,10 @@ public NetworkObjectSynchronizationTests(VariableLengthSafety variableLengthSafe protected override void OnCreatePlayerPrefab() { var component = m_PlayerPrefab.AddComponent(); -#if NGO_DAMODE if (m_DistributedAuthority) { component.SetWritePermissions(NetworkVariableWritePermission.Owner); } -#endif base.OnCreatePlayerPrefab(); } @@ -155,11 +152,7 @@ public IEnumerator NetworkObjectDeserializationFailure() var serverSideHostPlayerComponent = m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent(); var clientSidePlayerComponent = m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); var clientSideHostPlayerComponent = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ServerNetworkManager.LocalClientId].GetComponent(); -#if NGO_DAMODE var modeText = m_DistributedAuthority ? "owner" : "server"; -#else - var modeText = "server"; -#endif // Validate that the client side player values match the server side value of the client's player Assert.IsTrue(serverSideClientPlayerComponent.NetworkVariableData1.Value == clientSidePlayerComponent.NetworkVariableData1.Value, $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData1)}][Client Player-{clientSidePlayerComponent.OwnerClientId}] Client side value ({clientSidePlayerComponent.NetworkVariableData1.Value})" + @@ -174,11 +167,9 @@ public IEnumerator NetworkObjectDeserializationFailure() $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Client Player-{clientSidePlayerComponent.OwnerClientId}] Client side value ({clientSidePlayerComponent.NetworkVariableData4.Value})" + $" does not equal the {modeText} side value ({serverSideClientPlayerComponent.NetworkVariableData4.Value})!"); -#if NGO_DAMODE // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions // to only allow the service to write. For now, we will skip this validation for distributed authority if (!m_DistributedAuthority) -#endif { // Validate that only the 2nd and 4th NetworkVariable on the client side instance of the host's player is the same and the other two do not match // (i.e. NetworkVariables owned by the server should not get synchronized on client) @@ -228,11 +219,9 @@ public IEnumerator NetworkObjectDeserializationFailure() $"[{nameof(NetworkBehaviourWithOwnerNetworkVariables.NetworkVariableData4)}][Player-{clientOneId}] Client-{clientOneId} value ({clientSide1PlayerComponent.NetworkVariableData4.Value})" + $" does not equal Client-{clientTwoId}'s clone side value ({clientSide2Player1Clone.NetworkVariableData4.Value})!"); -#if NGO_DAMODE // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions // to only allow the service to write. For now, we will skip this validation for distributed authority if (!m_DistributedAuthority) -#endif { // Validate that client two's 2nd and 4th NetworkVariables for the local and clone instances match and the other two do not Assert.IsTrue(clientSide2PlayerComponent.NetworkVariableData1.Value != clientSide1Player2Clone.NetworkVariableData1.Value, @@ -253,11 +242,9 @@ public IEnumerator NetworkObjectDeserializationFailure() } } -#if NGO_DAMODE // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions // to only allow the service to write. For now, we will skip this validation for distributed authority if (!m_DistributedAuthority) -#endif { // Now validate all of the NetworkVariable values match to assure everything synchronized properly foreach (var spawnedObject in validSpawnedNetworkObjects) @@ -457,7 +444,6 @@ public override void OnNetworkSpawn() /// public class NetworkBehaviourWithOwnerNetworkVariables : NetworkBehaviour { -#if NGO_DAMODE private NetworkVariableWritePermission m_NetworkVariableWritePermission = NetworkVariableWritePermission.Server; /// /// For distributed authority, there is no such thing as a server and only owners @@ -476,8 +462,6 @@ public void SetWritePermissions(NetworkVariableWritePermission networkVariableWr // Should synchronize with everyone NetworkVariableData4 = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, networkVariableWritePermission); } -#endif - // Should not synchronize on non-owners public NetworkVariable NetworkVariableData1 = new NetworkVariable(default, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Server); @@ -491,13 +475,8 @@ public void SetWritePermissions(NetworkVariableWritePermission networkVariableWr public override void OnNetworkSpawn() { // Adjustment for distributed authority mode -#if NGO_DAMODE if ((m_NetworkVariableWritePermission == NetworkVariableWritePermission.Server && IsServer && !NetworkManager.DistributedAuthorityMode) || (m_NetworkVariableWritePermission == NetworkVariableWritePermission.Owner && IsOwner && NetworkManager.DistributedAuthorityMode)) -#else - if (IsServer) -#endif - { NetworkVariableData1.Value = Random.Range(1, 1000); NetworkVariableData2.Value = Random.Range(1, 1000); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index bb0413b8da..30325530c2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -126,10 +126,8 @@ public void TriggerRpc() } } -#if NGO_DAMODE [TestFixture(SessionModeTypes.ClientServer)] [TestFixture(SessionModeTypes.DistributedAuthority)] -#endif public class NetworkShowHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; @@ -145,9 +143,8 @@ public class NetworkShowHideTests : NetcodeIntegrationTest private NetworkObject m_Object2OnClient0; private NetworkObject m_Object3OnClient0; -#if NGO_DAMODE public NetworkShowHideTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif + protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("ShowHideObject"); @@ -427,11 +424,9 @@ public IEnumerator SpawnWithoutObserversTest() yield return WaitForTicks(m_ServerNetworkManager, 3); -#if NGO_DAMODE // 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 if (!m_DistributedAuthority) -#endif { // 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}!"); @@ -532,7 +527,7 @@ public IEnumerator NetworkHideChangeOwnership() yield return new WaitForSeconds(1.25f); LogAssert.NoUnexpectedReceived(); -#if NGO_DAMODE + // 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}!"); @@ -544,10 +539,6 @@ public IEnumerator NetworkHideChangeOwnership() { m_NetSpawnedObject1.NetworkShow(m_ClientNetworkManagers[1].LocalClientId); } -#else - // Show the object again to check nothing unexpected happens - m_NetSpawnedObject1.NetworkShow(m_ClientNetworkManagers[1].LocalClientId); -#endif yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 1); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs index 5bbcb1edcd..63a83dbd09 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs @@ -5,9 +5,7 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] public class NetworkSpawnManagerTests : NetcodeIntegrationTest { @@ -52,13 +50,11 @@ public IEnumerator TestServerCanAccessOtherPlayers() [Test] public void TestClientCantAccessServerPlayer() { -#if NGO_DAMODE if (m_DistributedAuthority) { VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_SessionModeType} mode."); return; } -#endif // client can't access server player Assert.Throws(() => { @@ -74,7 +70,7 @@ public void TestClientCanAccessOwnPlayer() Assert.NotNull(clientSideClientPlayerObject); Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId); } -#if NGO_DAMODE + [Test] public void TestClientCanAccessOtherPlayer() { @@ -88,18 +84,16 @@ public void TestClientCanAccessOtherPlayer() var otherClientPlayer = m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(otherClientSideClientId); Assert.NotNull(otherClientPlayer, $"Failed to obtain Client{otherClientSideClientId}'s player object!"); } -#endif [Test] public void TestClientCantAccessOtherPlayer() { -#if NGO_DAMODE if (m_DistributedAuthority) { VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_SessionModeType} mode."); return; } -#endif + // client can't access other player Assert.Throws(() => { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 79b9bec65d..24d139a73e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -9,38 +9,27 @@ using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost, MotionModels.UseTransform)] [TestFixture(HostOrServer.DAHost, MotionModels.UseRigidbody)] [TestFixture(HostOrServer.Host, MotionModels.UseTransform)] -#else - [TestFixture(HostOrServer.Host)] -#endif public class NetworkTransformOwnershipTests : IntegrationTestWithApproximation { -#if NGO_DAMODE public enum MotionModels { UseRigidbody, UseTransform } -#endif protected override int NumberOfClients => 1; private GameObject m_ClientNetworkTransformPrefab; private GameObject m_NetworkTransformPrefab; -#if NGO_DAMODE + private MotionModels m_MotionModel; -#endif -#if NGO_DAMODE public NetworkTransformOwnershipTests(HostOrServer hostOrServer, MotionModels motionModel) : base(hostOrServer) { m_MotionModel = motionModel; } -#else - public NetworkTransformOwnershipTests(HostOrServer hostOrServer) : base(hostOrServer) {} -#endif protected override void OnServerAndClientsCreated() { @@ -55,9 +44,7 @@ protected override void OnServerAndClientsCreated() // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results var networkRigidbody = m_ClientNetworkTransformPrefab.AddComponent(); -#if NGO_DAMODE networkRigidbody.UseRigidBodyForMotion = m_MotionModel == MotionModels.UseRigidbody; -#endif m_ClientNetworkTransformPrefab.AddComponent(); m_NetworkTransformPrefab = CreateNetworkObjectPrefab("ServerAuthorityTest"); @@ -68,9 +55,7 @@ protected override void OnServerAndClientsCreated() // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results networkRigidbody = m_NetworkTransformPrefab.AddComponent(); -#if NGO_DAMODE networkRigidbody.UseRigidBodyForMotion = m_MotionModel == MotionModels.UseRigidbody; -#endif m_NetworkTransformPrefab.AddComponent(); networkTransform.Interpolate = false; networkTransform.UseHalfFloatPrecision = false; @@ -153,8 +138,6 @@ public IEnumerator LateJoinedNonOwnerClientCannotChangeTransform() // Wait one frame so the NetworkTransform can apply the owner's last state received on the late joining client side // (i.e. prevent the non-owner from changing the transform) - -#if NGO_DAMODE if (m_MotionModel == MotionModels.UseRigidbody) { // Allow fixed update to run twice for values to propogate to Unity transform @@ -162,7 +145,6 @@ public IEnumerator LateJoinedNonOwnerClientCannotChangeTransform() yield return new WaitForFixedUpdate(); } else -#endif { yield return null; } @@ -258,7 +240,6 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn // Change ownership and wait for the non-owner to reflect the change VerifyObjectIsSpawnedOnClient.ResetObjectTable(); -#if NGO_DAMODE if (m_DistributedAuthority) { ownerInstance.NetworkObject.ChangeOwnership(networkManagerNonOwner.LocalClientId); @@ -267,9 +248,6 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn { m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent(), networkManagerNonOwner.LocalClientId, true); } -#else - m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent(), networkManagerNonOwner.LocalClientId); -#endif yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent().OwnerClientId == networkManagerNonOwner.LocalClientId); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!"); @@ -298,7 +276,7 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn valueSetByOwner = Vector3.one * 10; ownerInstance.transform.localScale = valueSetByOwner; rotation.eulerAngles = valueSetByOwner; -#if NGO_DAMODE + // Allow scale to update first when using rigid body motion if (m_MotionModel == MotionModels.UseRigidbody) { @@ -313,7 +291,6 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn rigidBody.Move(valueSetByOwner, rotation); } else -#endif { m_UseAdjustedVariance = false; ownerInstance.transform.position = valueSetByOwner; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs index bb8d103868..69bf8b1e40 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs @@ -14,14 +14,12 @@ namespace Unity.Netcode.RuntimeTests /// models for each operating mode when packet loss and latency is /// present. /// -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] -#endif [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 7013444d94..65e16bdb03 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -9,14 +9,12 @@ namespace Unity.Netcode.RuntimeTests /// server and host operating modes and will test both authoritative /// models for each operating mode. /// -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] -#endif [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs index f908b0fa92..c7ee2afdf6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs @@ -6,9 +6,7 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] public class NetworkVarBufferCopyTest : NetcodeIntegrationTest { @@ -79,20 +77,16 @@ public DummyNetVar( public class DummyNetBehaviour : NetworkBehaviour { -#if NGO_DAMODE public static bool DistributedAuthority; -#endif public DummyNetVar NetVar; private void Awake() { -#if NGO_DAMODE if (DistributedAuthority) { NetVar = new DummyNetVar(writePerm: NetworkVariableWritePermission.Owner); } else -#endif { NetVar = new DummyNetVar(); } @@ -100,17 +94,10 @@ private void Awake() public override void OnNetworkSpawn() { -#if NGO_DAMODE if ((NetworkManager.DistributedAuthorityMode && !IsOwner) || (!NetworkManager.DistributedAuthorityMode && !IsServer)) { ClientDummyNetBehaviourSpawned(this); } -#else - if (!IsServer) - { - ClientDummyNetBehaviourSpawned(this); - } -#endif base.OnNetworkSpawn(); } } @@ -132,13 +119,8 @@ protected override IEnumerator OnSetup() protected override void OnCreatePlayerPrefab() { - -#if NGO_DAMODE DummyNetBehaviour.DistributedAuthority = m_DistributedAuthority; m_PlayerPrefab.AddComponent(); -#else - m_PlayerPrefab.AddComponent(); -#endif } [UnityTest] @@ -167,13 +149,8 @@ public IEnumerator TestEntireBufferIsCopiedOnNetworkVariableDelta() Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client side DummyNetBehaviour to register it was spawned!"); // Check that FieldWritten is written when dirty -#if NGO_DAMODE var authorityComponent = m_DistributedAuthority ? clientComponent : serverComponent; var nonAuthorityComponent = m_DistributedAuthority ? serverComponent : clientComponent; -#else - var authorityComponent = serverComponent; - var nonAuthorityComponent = clientComponent; -#endif authorityComponent.NetVar.SetDirty(true); yield return s_DefaultWaitForTick; Assert.True(authorityComponent.NetVar.FieldWritten); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 36a2724470..7bee2a8acd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -18,13 +18,11 @@ public static IEnumerable TestDataSource() { foreach (HostOrServer hostOrServer in Enum.GetValues(typeof(HostOrServer))) { -#if NGO_DAMODE - // DAMODE-TODO: Add support for distributed authority mode + // DANGO-EXP TODO: Add support for distributed authority mode if (hostOrServer == HostOrServer.DAHost) - { + { continue; } -#endif yield return new TestFixtureData(hostOrServer); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs index 897f303308..037481c550 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs @@ -7,15 +7,10 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(SceneManagementState.SceneManagementEnabled, SessionModeTypes.DistributedAuthority)] [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.DistributedAuthority)] [TestFixture(SceneManagementState.SceneManagementEnabled, SessionModeTypes.ClientServer)] [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.ClientServer)] -#else - [TestFixture(SceneManagementState.SceneManagementEnabled)] - [TestFixture(SceneManagementState.SceneManagementDisabled)] -#endif public class NetworkVisibilityTests : NetcodeIntegrationTest { @@ -23,11 +18,7 @@ public class NetworkVisibilityTests : NetcodeIntegrationTest private GameObject m_TestNetworkPrefab; private bool m_SceneManagementEnabled; -#if NGO_DAMODE public NetworkVisibilityTests(SceneManagementState sceneManagementState, SessionModeTypes sessionModeType) : base(sessionModeType) -#else - public NetworkVisibilityTests(SceneManagementState sceneManagementState) -#endif { m_SceneManagementEnabled = sceneManagementState == SceneManagementState.SceneManagementEnabled; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs index 7bf21e9341..6de84b1be1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs @@ -73,9 +73,8 @@ public void InitializeLastCient() NetworkUpdateLoop.RegisterAllNetworkUpdates(this); } } -#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] public class OwnerModifiedTests : NetcodeIntegrationTest { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index 8d32ebb553..c7b2fa5013 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -8,19 +8,14 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(RigidbodyInterpolation.Interpolate, true, true)] // This should be allowed under all condistions when using Rigidbody motion [TestFixture(RigidbodyInterpolation.Extrapolate, true, true)] // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation [TestFixture(RigidbodyInterpolation.Extrapolate, false, true)] // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation [TestFixture(RigidbodyInterpolation.Interpolate, true, false)] // This should not allow kinematic instances to have Rigidbody interpolation enabled [TestFixture(RigidbodyInterpolation.Interpolate, false, false)] // Testing that rigid body interpolation remains the same if NT interpolate is disabled -#endif - public class NetworkRigidbodyTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; - -#if NGO_DAMODE private bool m_NetworkTransformInterpolate; private bool m_UseRigidBodyForMotion; private RigidbodyInterpolation m_RigidbodyInterpolation; @@ -31,23 +26,15 @@ public NetworkRigidbodyTest(RigidbodyInterpolation rigidbodyInterpolation, bool m_NetworkTransformInterpolate = networkTransformInterpolate; m_UseRigidBodyForMotion = useRigidbodyForMotion; } -#endif protected override void OnCreatePlayerPrefab() { -#if NGO_DAMODE var networkTransform = m_PlayerPrefab.AddComponent(); networkTransform.Interpolate = m_NetworkTransformInterpolate; var rigidbody = m_PlayerPrefab.AddComponent(); rigidbody.interpolation = m_RigidbodyInterpolation; var networkRigidbody = m_PlayerPrefab.AddComponent(); networkRigidbody.UseRigidBodyForMotion = m_UseRigidBodyForMotion; -#else - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.GetComponent().interpolation = RigidbodyInterpolation.Interpolate; -#endif } /// @@ -68,7 +55,7 @@ public IEnumerator TestRigidbodyKinematicEnableDisable() var serverClientInstanceRigidBody = serverClientPlayerInstance.GetComponent(); var clientRigidBody = clientPlayerInstance.GetComponent(); -#if NGO_DAMODE + if (m_UseRigidBodyForMotion) { // Server authoritative NT should yield non-kinematic mode for the server-side player instance @@ -86,7 +73,6 @@ public IEnumerator TestRigidbodyKinematicEnableDisable() $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); } else -#endif { // server rigidbody has authority and should not be kinematic Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); @@ -96,7 +82,6 @@ public IEnumerator TestRigidbodyKinematicEnableDisable() // Server authoritative NT should yield kinematic mode for the client-side player instance Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); -#if NGO_DAMODE // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation if (!m_NetworkTransformInterpolate) { @@ -104,7 +89,6 @@ public IEnumerator TestRigidbodyKinematicEnableDisable() $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); } else -#endif { Assert.AreEqual(RigidbodyInterpolation.None, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.None)}!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs index 96f4eff638..2543c4d524 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs @@ -6,9 +6,7 @@ namespace Unity.Netcode.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class PlayerObjectTests : NetcodeIntegrationTest @@ -28,10 +26,8 @@ protected override void OnServerAndClientsCreated() [UnityTest] public IEnumerator SpawnAndReplaceExistingPlayerObject() { -#if NGO_DAMODE yield return WaitForConditionOrTimeOut(() => m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_ClientNetworkManagers[0].LocalClientId)); AssertOnTimeout("Timed out waiting for client-side player object to spawn!"); -#endif // Get the server-side player NetworkObject var originalPlayer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; // Get the client-side player NetworkObject @@ -40,12 +36,8 @@ public IEnumerator SpawnAndReplaceExistingPlayerObject() // Create a new player prefab instance var newPlayer = Object.Instantiate(m_NewPlayerToSpawn); var newPlayerNetworkObject = newPlayer.GetComponent(); -#if NGO_DAMODE // In distributed authority mode, the client owner spawns its new player newPlayerNetworkObject.NetworkManagerOwner = m_DistributedAuthority ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; -#else - newPlayerNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; -#endif // Spawn this instance as a new player object for the client who already has an assigned player object newPlayerNetworkObject.SpawnAsPlayerObject(m_ClientNetworkManagers[0].LocalClientId); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs index 2f2a2a3d6a..d163213200 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -96,7 +96,6 @@ public void DefaultToClientsAndHostRpc() OnRpcReceived(); } -#if NGO_DAMODE [Rpc(SendTo.Authority)] public void DefaultToAuthorityRpc() { @@ -108,7 +107,6 @@ public void DefaultToNotAuthorityRpc() { OnRpcReceived(); } -#endif // RPCs with parameters @@ -160,7 +158,6 @@ public void DefaultToClientsAndHostWithParamsRpc(int i, bool b, float f, string OnRpcReceivedWithParams(i, b, f, s); } -#if NGO_DAMODE [Rpc(SendTo.Authority)] public void DefaultToAuthorityWithParamsRpc(int i, bool b, float f, string s) { @@ -172,7 +169,6 @@ public void DefaultToNotAuthorityWithParamsRpc(int i, bool b, float f, string s) { OnRpcReceivedWithParams(i, b, f, s); } -#endif // RPCs with RPC parameters @@ -232,7 +228,6 @@ public void DefaultToClientsAndHostWithRpcParamsRpc(RpcParams rpcParams) ReceivedFrom = rpcParams.Receive.SenderClientId; } -#if NGO_DAMODE [Rpc(SendTo.Authority)] public void DefaultToAuthorityWithRpcParamsRpc(RpcParams rpcParams) { @@ -246,8 +241,6 @@ public void DefaultToNotAuthorityWithRpcParamsRpc(RpcParams rpcParams) OnRpcReceived(); ReceivedFrom = rpcParams.Receive.SenderClientId; } -#endif - // RPCs with parameters and RPC parameters @@ -299,7 +292,6 @@ public void DefaultToClientsAndHostWithParamsAndRpcParamsRpc(int i, bool b, floa OnRpcReceivedWithParams(i, b, f, s); } -#if NGO_DAMODE [Rpc(SendTo.Authority)] public void DefaultToAuthorityWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) { @@ -311,7 +303,6 @@ public void DefaultToNotAuthorityWithParamsAndRpcParamsRpc(int i, bool b, float { OnRpcReceivedWithParams(i, b, f, s); } -#endif // RPCs with AllowTargetOverride = true @@ -371,7 +362,6 @@ public void DefaultToClientsAndHostAllowOverrideRpc(RpcParams rpcParams) OnRpcReceived(); } -#if NGO_DAMODE [Rpc(SendTo.Authority, AllowTargetOverride = true)] public void DefaultToAuthorityAllowOverrideRpc(RpcParams rpcParams) { @@ -383,7 +373,6 @@ public void DefaultToNotAuthorityAllowOverrideRpc(RpcParams rpcParams) { OnRpcReceived(); } -#endif // RPCs with DeferLocal = true @@ -429,7 +418,6 @@ public void DefaultToClientsAndHostDeferLocalRpc(RpcParams rpcParams) OnRpcReceived(); } -#if NGO_DAMODE [Rpc(SendTo.Authority, DeferLocal = true)] public void DefaultToAuthorityDeferLocalRpc(RpcParams rpcParams) { @@ -441,7 +429,6 @@ public void DefaultToNotAuthorityDeferLocalRpc(RpcParams rpcParams) { OnRpcReceived(); } -#endif // RPCs with RequireOwnership = true @@ -499,7 +486,6 @@ public void SpecifiedInParamsRequireOwnershipRpc(RpcParams rpcParams) OnRpcReceived(); } -#if NGO_DAMODE [Rpc(SendTo.Authority, RequireOwnership = true)] public void DefaultToAuthorityRequireOwnershipRpc() { @@ -511,8 +497,6 @@ public void DefaultToNotAuthorityRequireOwnershipRpc() { OnRpcReceived(); } -#endif - // Mutual RPC Recursion @@ -914,7 +898,6 @@ public void VerifySentToNotMe(ulong objectOwner, ulong sender, string methodName VerifySentToNotId(objectOwner, sender, sender, methodName, false); } -#if NGO_DAMODE public void VerifySentToAuthority(ulong objectOwner, ulong sender, string methodName) { var receiver = objectOwner; @@ -934,7 +917,6 @@ public void VerifySentToNotAuthority(ulong objectOwner, ulong sender, string met } VerifySentToNotId(objectOwner, sender, receiver, methodName, false); } -#endif public void VerifySentToOwnerWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) { @@ -978,7 +960,6 @@ public void VerifySentToNotMeWithReceivedFrom(ulong objectOwner, ulong sender, s VerifySentToNotId(objectOwner, sender, sender, methodName, true); } -#if NGO_DAMODE public void VerifySentToAuthorityWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) { var receiver = objectOwner; @@ -998,7 +979,6 @@ public void VerifySentToNotAuthorityWithReceivedFrom(ulong objectOwner, ulong se } VerifySentToNotId(objectOwner, sender, receiver, methodName, true); } -#endif public void VerifySentToOwnerWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) { @@ -1042,7 +1022,6 @@ public void VerifySentToNotMeWithParams(ulong objectOwner, ulong sender, string VerifySentToNotIdWithParams(objectOwner, sender, sender, methodName, i, b, f, s); } -#if NGO_DAMODE public void VerifySentToAuthorityWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) { var receiver = objectOwner; @@ -1062,7 +1041,6 @@ public void VerifySentToNotAuthorityWithParams(ulong objectOwner, ulong sender, } VerifySentToNotIdWithParams(objectOwner, sender, receiver, methodName, i, b, f, s); } -#endif public void RethrowTargetInvocationException(Action action) { @@ -1077,9 +1055,7 @@ public void RethrowTargetInvocationException(Action action) } } -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSendingNoOverride : UniversalRpcTestsBase @@ -1092,12 +1068,8 @@ public UniversalRpcTestSendingNoOverride(HostOrServer hostOrServer) : base(hostO [Test] public void TestSendingNoOverride( // Excludes SendTo.SpecifiedInParams -#if NGO_DAMODE [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, -#else - [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, -#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1115,9 +1087,7 @@ public void TestSendingNoOverride( } -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSenderClientId : UniversalRpcTestsBase @@ -1130,11 +1100,7 @@ public UniversalRpcTestSenderClientId(HostOrServer hostOrServer) : base(hostOrSe [Test] public void TestSenderClientId( // Excludes SendTo.SpecifiedInParams -#if NGO_DAMODE [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, -#else - [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, -#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1152,9 +1118,7 @@ public void TestSenderClientId( } -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSendingNoOverrideWithParams : UniversalRpcTestsBase @@ -1167,11 +1131,7 @@ public UniversalRpcTestSendingNoOverrideWithParams(HostOrServer hostOrServer) : [Test] public void TestSendingNoOverrideWithParams( // Excludes SendTo.SpecifiedInParams -#if NGO_DAMODE [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, -#else - [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, -#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1201,9 +1161,7 @@ public void TestSendingNoOverrideWithParams( } -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSendingNoOverrideWithParamsAndRpcParams : UniversalRpcTestsBase @@ -1216,11 +1174,7 @@ public UniversalRpcTestSendingNoOverrideWithParamsAndRpcParams(HostOrServer host [Test] public void TestSendingNoOverrideWithParamsAndRpcParams( // Excludes SendTo.SpecifiedInParams -#if NGO_DAMODE [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, -#else - [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, -#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1250,9 +1204,8 @@ public void TestSendingNoOverrideWithParamsAndRpcParams( } -#if NGO_DAMODE + [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestRequireOwnership : UniversalRpcTestsBase @@ -1265,11 +1218,7 @@ public UniversalRpcTestRequireOwnership(HostOrServer hostOrServer) : base(hostOr [Test] public void TestRequireOwnership( // Excludes SendTo.SpecifiedInParams -#if NGO_DAMODE [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, -#else - [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, -#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender ) @@ -1293,9 +1242,7 @@ public void TestRequireOwnership( } } -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestDisallowedOverride : UniversalRpcTestsBase @@ -1309,11 +1256,7 @@ public UniversalRpcTestDisallowedOverride(HostOrServer hostOrServer) : base(host [Test] public void TestDisallowedOverride( // Excludes SendTo.SpecifiedInParams -#if NGO_DAMODE [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost, SendTo.Authority, SendTo.NotAuthority)] SendTo sendTo, -#else - [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, -#endif [Values(0u, 1u, 2u)] ulong objectOwner, [Values(0u, 1u, 2u)] ulong sender) { @@ -1602,9 +1545,7 @@ public UniversalRpcTestDefaultSendToSpecifiedInParamsSendingToServerAndOwner(Hos } } -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestDeferLocal : UniversalRpcTestsBase @@ -1661,8 +1602,6 @@ public UniversalRpcTestDeferLocal(HostOrServer hostOrServer) : base(hostOrServer [TestCase(SendTo.ClientsAndHost, 2u, 0u)] [TestCase(SendTo.ClientsAndHost, 2u, 1u)] [TestCase(SendTo.ClientsAndHost, 2u, 2u)] - -#if NGO_DAMODE [TestCase(SendTo.Authority, 0u, 0u)] [TestCase(SendTo.Authority, 1u, 1u)] [TestCase(SendTo.Authority, 2u, 2u)] @@ -1672,7 +1611,6 @@ public UniversalRpcTestDeferLocal(HostOrServer hostOrServer) : base(hostOrServer [TestCase(SendTo.NotAuthority, 1u, 2u)] [TestCase(SendTo.NotAuthority, 2u, 0u)] [TestCase(SendTo.NotAuthority, 2u, 1u)] -#endif public void TestDeferLocal( SendTo defaultSendTo, ulong objectOwner, @@ -1686,13 +1624,11 @@ ulong sender return; } -#if NGO_DAMODE // Similar to above, since Server and NotServer are already tested we can consider this a success if (!m_DistributedAuthority && defaultSendTo == SendTo.Authority || defaultSendTo == SendTo.NotAuthority) { return; } -#endif var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc"; var verifyMethodName = $"VerifySentTo{defaultSendTo}"; @@ -1756,7 +1692,6 @@ ulong sender [TestCase(SendTo.ClientsAndHost, 2u, 0u)] [TestCase(SendTo.ClientsAndHost, 2u, 1u)] [TestCase(SendTo.ClientsAndHost, 2u, 2u)] -#if NGO_DAMODE [TestCase(SendTo.Authority, 0u, 0u)] [TestCase(SendTo.Authority, 1u, 1u)] [TestCase(SendTo.Authority, 2u, 2u)] @@ -1766,7 +1701,6 @@ ulong sender [TestCase(SendTo.NotAuthority, 1u, 2u)] [TestCase(SendTo.NotAuthority, 2u, 0u)] [TestCase(SendTo.NotAuthority, 2u, 1u)] -#endif public void TestDeferLocalOverrideToTrue( SendTo defaultSendTo, ulong objectOwner, @@ -1779,13 +1713,11 @@ ulong sender // Just consider this case a success... return; } -#if NGO_DAMODE // Similar to above, since Server and NotServer are already tested we can consider this a success if (!m_DistributedAuthority && defaultSendTo == SendTo.Authority || defaultSendTo == SendTo.NotAuthority) { return; } -#endif var sendMethodName = $"DefaultTo{defaultSendTo}WithRpcParamsRpc"; var verifyMethodName = $"VerifySentTo{defaultSendTo}"; @@ -1849,7 +1781,6 @@ ulong sender [TestCase(SendTo.ClientsAndHost, 2u, 0u)] [TestCase(SendTo.ClientsAndHost, 2u, 1u)] [TestCase(SendTo.ClientsAndHost, 2u, 2u)] -#if NGO_DAMODE [TestCase(SendTo.Authority, 0u, 0u)] [TestCase(SendTo.Authority, 1u, 1u)] [TestCase(SendTo.Authority, 2u, 2u)] @@ -1859,7 +1790,6 @@ ulong sender [TestCase(SendTo.NotAuthority, 1u, 2u)] [TestCase(SendTo.NotAuthority, 2u, 0u)] [TestCase(SendTo.NotAuthority, 2u, 1u)] -#endif public void TestDeferLocalOverrideToFalse( SendTo defaultSendTo, ulong objectOwner, @@ -1872,13 +1802,11 @@ ulong sender // Just consider this case a success... return; } -#if NGO_DAMODE // Similar to above, since Server and NotServer are already tested we can consider this a success if (!m_DistributedAuthority && defaultSendTo == SendTo.Authority || defaultSendTo == SendTo.NotAuthority) { return; } -#endif var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc"; var verifyMethodName = $"VerifySentTo{defaultSendTo}"; diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs index 0f8d756e74..4164993e95 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs @@ -127,11 +127,7 @@ private void RemoveParent(Transform child, bool worldPositionStays = true) /// private bool CheckForAuthority() { -#if NGO_DAMODE return NetworkObject.HasAuthority; -#else - return IsServer; -#endif } public void DeparentAllChildren(bool worldPositionStays = true) @@ -279,11 +275,7 @@ private void LateUpdate() public void CheckChildren() { -#if NGO_DAMODE if (!NetworkObject.HasAuthority || m_RequestSent || AuthorityRootParent != this) -#else - if (!NetworkManager.IsServer || m_RequestSent || AuthorityRootParent != this) -#endif { return; } diff --git a/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs b/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs index 8343b9d7f2..6aa6985962 100644 --- a/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs +++ b/testproject/Assets/Tests/Manual/Scripts/ParentPlayerToInSceneNetworkObject.cs @@ -9,13 +9,11 @@ public override void OnNetworkSpawn() { if (IsServer && IsOwner) { -#if NGO_DAMODE // Client-Server mode, the server handles parenting the players if (!NetworkManager.DistributedAuthorityMode) { NetworkManager.OnClientConnectedCallback += NetworkManager_OnClientConnectedCallback; } -#endif // Server player is parented under this NetworkObject SetPlayerParent(NetworkManager.LocalClientId); } @@ -34,38 +32,15 @@ private void SetPlayerParent(ulong clientId) { if (IsSpawned) { -#if NGO_DAMODE var playerObject = NetworkManager.SpawnManager.GetPlayerNetworkObject(clientId); if (playerObject.gameObject.scene != gameObject.scene) { SceneManager.MoveGameObjectToScene(playerObject.gameObject, gameObject.scene); } playerObject.TrySetParent(NetworkObject, false); -#else - // As long as the client (player) is in the connected clients list - if (NetworkManager.ConnectedClients.ContainsKey(clientId)) - { - var playerObject = NetworkManager.ConnectedClients[clientId].PlayerObject; - if (playerObject.gameObject.scene != gameObject.scene) - { - SceneManager.MoveGameObjectToScene(playerObject.gameObject, gameObject.scene); - } - // Set the player as a child of this in-scene placed NetworkObject - // We parent in local space by setting the WorldPositionStays value to false - NetworkManager.ConnectedClients[clientId].PlayerObject.TrySetParent(NetworkObject, false); - } -#endif } } -#if NGO_DAMODE - [ServerRpc(RequireOwnership = false)] - private void SetParentServerRpc() - { - SetPlayerParent(NetworkManager.LocalClientId); - } -#endif - private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) { // OnSceneEvent is very useful for many things diff --git a/testproject/Assets/Tests/Runtime/AddressablesTests.cs b/testproject/Assets/Tests/Runtime/AddressablesTests.cs index 1d0f5824dc..8af531b0b2 100644 --- a/testproject/Assets/Tests/Runtime/AddressablesTests.cs +++ b/testproject/Assets/Tests/Runtime/AddressablesTests.cs @@ -12,9 +12,7 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class AddressablesTests : NetcodeIntegrationTest @@ -36,7 +34,6 @@ protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() return NetworkManagerInstatiationMode.DoNotCreate; } - protected override void OnInlineTearDown() { ShutdownAndCleanUp(); diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index f68e05916d..ff87a0a790 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -19,9 +19,7 @@ namespace TestProject.RuntimeTests /// Possibly we could build this at runtime, but for now it uses the same animator controller as the manual /// test does. /// -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost, OwnerShipMode.ClientOwner, AuthoritativeMode.OwnerAuth)] -#endif [TestFixture(HostOrServer.Host, OwnerShipMode.ServerOwner, AuthoritativeMode.OwnerAuth)] [TestFixture(HostOrServer.Host, OwnerShipMode.ServerOwner, AuthoritativeMode.ServerAuth)] [TestFixture(HostOrServer.Host, OwnerShipMode.ClientOwner, AuthoritativeMode.OwnerAuth)] diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs index 9cb93d6efa..11f194eeed 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs @@ -9,10 +9,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class ClientSynchronizationValidationTest : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -24,10 +22,8 @@ public class ClientSynchronizationValidationTest : NetcodeIntegrationTest private bool m_RuntimeSceneWasExcludedFromSynch; private List m_ClientSceneVerifiers = new List(); - -#if NGO_DAMODE public ClientSynchronizationValidationTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif + protected override void OnNewClientStarted(NetworkManager networkManager) { if (m_IncludeSceneVerificationHandler) diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index 3d4f467946..cf82da52d2 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -12,10 +12,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class InScenePlacedNetworkObjectTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -24,9 +22,8 @@ public class InScenePlacedNetworkObjectTests : NetcodeIntegrationTest private Scene m_ServerSideSceneLoaded; private bool m_CanStartServerAndClients; -#if NGO_DAMODE public InScenePlacedNetworkObjectTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif + protected override IEnumerator OnSetup() { NetworkObjectTestComponent.Reset(); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs index c3fefb36ea..505088dd8e 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventCallbacks.cs @@ -10,9 +10,7 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkSceneManagerEventCallbacks : NetcodeIntegrationTest diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs index 204fdd28b5..084c58ac75 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs @@ -10,10 +10,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost, LoadSceneMode.Single)] [TestFixture(HostOrServer.DAHost, LoadSceneMode.Additive)] -#endif [TestFixture(HostOrServer.Host, LoadSceneMode.Single)] [TestFixture(HostOrServer.Host, LoadSceneMode.Additive)] [TestFixture(HostOrServer.Server, LoadSceneMode.Single)] diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs index 5afba0af57..cae4bed259 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs @@ -10,9 +10,7 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkSceneManagerEventNotifications : NetcodeIntegrationTest diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs index 6243dec581..f8bb06ad0b 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs @@ -10,10 +10,9 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -21,9 +20,7 @@ public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest protected Dictionary m_InSceneObjectList = new Dictionary(); -#if NGO_DAMODE public NetworkSceneManagerPopulateInSceneTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override IEnumerator OnSetup() { diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs index cbdc02ef33..b7e606bd31 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs @@ -9,10 +9,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost, LoadSceneMode.Single)] [TestFixture(HostOrServer.DAHost, LoadSceneMode.Additive)] -#endif [TestFixture(HostOrServer.Host, LoadSceneMode.Single)] [TestFixture(HostOrServer.Host, LoadSceneMode.Additive)] [TestFixture(HostOrServer.Server, LoadSceneMode.Single)] diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs index dbc9a4cb41..9b330d79ce 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs @@ -7,9 +7,7 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(HostOrServer.DAHost)] -#endif [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkSceneManagerUsageTests : NetcodeIntegrationTest @@ -69,12 +67,10 @@ public IEnumerator ClientCannotUseException([Values(LoadSceneMode.Single, LoadSc m_CurrentSceneName = k_AdditiveScene1; var statusResult = m_ClientNetworkManagers[0].SceneManager.LoadScene(m_CurrentSceneName, loadSceneMode); var expectedResult = SceneEventProgressStatus.ServerOnlyAction; -#if NGO_DAMODE if (m_DistributedAuthority) { expectedResult = SceneEventProgressStatus.SessionOwnerOnlyAction; } -#endif Assert.True(statusResult == expectedResult, $"[Client][Load][{loadSceneMode}] Failed to receive a {nameof(expectedResult)} response!"); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs index 001cba5c94..3d17ce87b9 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs @@ -10,10 +10,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class SceneEventProgressTests : NetcodeIntegrationTest { private const string k_SceneUsedToGetAsyncOperation = "EmptyScene"; @@ -27,9 +25,7 @@ public class SceneEventProgressTests : NetcodeIntegrationTest private List m_ClientThatShouldNotHaveCompleted = new List(); private List m_ClientThatShouldHaveCompleted = new List(); -#if NGO_DAMODE public SceneEventProgressTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif private bool SceneEventProgressComplete(SceneEventProgress sceneEventProgress) { diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs index 6d8b99a7fa..be0a1d5168 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs @@ -10,10 +10,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class NetworkObjectParentingTests { private const int k_ClientInstanceCount = 1; @@ -34,13 +32,11 @@ public class NetworkObjectParentingTests private Scene m_InitScene; private Scene m_TestScene; -#if NGO_DAMODE private SessionModeTypes m_SessionModeType; public NetworkObjectParentingTests(SessionModeTypes sessionModeType) { m_SessionModeType = sessionModeType; } -#endif private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { @@ -75,7 +71,6 @@ public IEnumerator Setup() Assert.That(m_ClientNetworkManagers, Is.Not.Null); Assert.That(m_ClientNetworkManagers.Length, Is.EqualTo(k_ClientInstanceCount)); -#if NGO_DAMODE m_ServerNetworkManager.NetworkConfig.SessionMode = m_SessionModeType; m_ServerNetworkManager.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_SessionModeType == SessionModeTypes.DistributedAuthority; @@ -84,7 +79,7 @@ public IEnumerator Setup() client.NetworkConfig.SessionMode = m_SessionModeType; client.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_SessionModeType == SessionModeTypes.DistributedAuthority; } -#endif + m_Dude_NetObjs = new Transform[setCount]; m_Dude_LeftArm_NetObjs = new Transform[setCount]; m_Dude_RightArm_NetObjs = new Transform[setCount]; diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs index 3aa21cc412..e1f6dbbded 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs @@ -1,8 +1,6 @@ using System.Collections; using System.Collections.Generic; -#if NGO_DAMODE using NUnit.Framework; -#endif using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -29,10 +27,8 @@ public override void OnNetworkSpawn() } } -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class ParentDynamicUnderInScenePlaced : NetcodeIntegrationTest { private const string k_SceneToLoad = "GenericInScenePlacedObject"; @@ -40,9 +36,7 @@ public class ParentDynamicUnderInScenePlaced : NetcodeIntegrationTest private GameObject m_DynamicallySpawned; private bool m_SceneIsLoaded; -#if NGO_DAMODE public ParentDynamicUnderInScenePlaced(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override IEnumerator OnSetup() { @@ -111,9 +105,7 @@ private bool TestParentedAndNotInScenePlaced() return true; } -#if NGO_DAMODE private ulong m_TargetScenePlacedId; -#endif /// /// Integration test that validates parenting dynamically spawned NetworkObjects /// under an in-scene placed NetworkObject works properly. @@ -136,9 +128,7 @@ public IEnumerator ParentUnderInSceneplaced() // Wait for the host-server's player to be parented under the in-scene placed NetworkObject yield return WaitForConditionOrTimeOut(TestParentedAndNotInScenePlaced); AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.IsSceneObject.Value}) | Was Parented ({m_FailedValidation.transform.position != null})"); -#if NGO_DAMODE m_TargetScenePlacedId = m_ServerNetworkManager.LocalClient.PlayerObject.transform.parent.GetComponent().NetworkObjectId; -#endif // Now dynamically spawn a NetworkObject to also test dynamically spawned NetworkObjects being parented // under in-scene placed NetworkObjects @@ -148,19 +138,16 @@ public IEnumerator ParentUnderInSceneplaced() for (int i = 0; i < 5; i++) { yield return CreateAndStartNewClient(); -#if NGO_DAMODE if (m_DistributedAuthority) { yield return WaitForConditionOrTimeOut(() => ParentPlayerToInSceneNetworkObjectFound(i)); AssertOnTimeout($"[Client-{i + 1}] Failed to find in-scene placed NetworkObject or failed to parent under it!"); } -#endif yield return WaitForConditionOrTimeOut(TestParentedAndNotInScenePlaced); AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.IsSceneObject.Value}) | Was Parented ({m_FailedValidation.transform.position != null})"); } } -#if NGO_DAMODE /// /// For distributed authority mode, we have to find the in-scene placed object to parent the newly spawned player under /// @@ -188,7 +175,7 @@ private bool ParentPlayerToInSceneNetworkObjectFound(int index) return true; } -#endif + private void SceneManager_OnLoadComplete(ulong clientId, string sceneName, LoadSceneMode loadSceneMode) { if (clientId == m_ServerNetworkManager.LocalClientId && sceneName == k_SceneToLoad) diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index b9f4f6f47c..28fe1e2d40 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -10,10 +10,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation { private const string k_BaseSceneToLoad = "UnitTestBaseScene"; @@ -26,9 +24,8 @@ public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation protected override int NumberOfClients => 2; -#if NGO_DAMODE public ParentingInSceneObjectsTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif + protected override void OnOneTimeSetup() { NetworkManagerTestDisabler.IsIntegrationTest = true; diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs index 0d911dc5d7..4168eea071 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs @@ -11,10 +11,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class ParentingWorldPositionStaysTests : IntegrationTestWithApproximation { private const int k_NestedChildren = 10; @@ -160,9 +158,7 @@ public enum NetworkTransformSettings private Quaternion m_ChildStartRotation = Quaternion.Euler(-35.0f, 0.0f, -180.0f); private Vector3 m_ChildStartScale = Vector3.one; -#if NGO_DAMODE public ParentingWorldPositionStaysTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override IEnumerator OnSetup() { diff --git a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs index 90d728dfbf..6fb3925b80 100644 --- a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs +++ b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs @@ -8,10 +8,8 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest { public const string SceneToLoad = "InSceneNetworkObject"; @@ -19,9 +17,7 @@ public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest protected override int NumberOfClients => 0; protected Scene m_SceneLoaded; -#if NGO_DAMODE public RespawnInSceneObjectsAfterShutdown(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override void OnOneTimeSetup() { diff --git a/testproject/Assets/Tests/Runtime/SenderIdTests.cs b/testproject/Assets/Tests/Runtime/SenderIdTests.cs index 591469145b..e25b029b5f 100644 --- a/testproject/Assets/Tests/Runtime/SenderIdTests.cs +++ b/testproject/Assets/Tests/Runtime/SenderIdTests.cs @@ -10,10 +10,9 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class SenderIdTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -21,9 +20,7 @@ public class SenderIdTests : NetcodeIntegrationTest private NetworkManager FirstClient => m_ClientNetworkManagers[0]; private NetworkManager SecondClient => m_ClientNetworkManagers[1]; -#if NGO_DAMODE public SenderIdTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif [UnityTest] public IEnumerator WhenSendingMessageFromServerToClient_SenderIdIsCorrect() diff --git a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs index a55b943ad1..2cfda8d6d6 100644 --- a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs +++ b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs @@ -8,17 +8,14 @@ namespace TestProject.RuntimeTests { -#if NGO_DAMODE + [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] -#endif public class ServerDisconnectsClientTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; -#if NGO_DAMODE public ServerDisconnectsClientTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } -#endif protected override void OnCreatePlayerPrefab() { From 43bc25d4c7eb4804c82ee9dae8a928e37007d6cc Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 19:12:17 -0500 Subject: [PATCH 012/236] update Removing missed NGO_DAMODE define --- .../TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index 4c9cfbd695..a29606688c 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -410,7 +410,6 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan { networkManager.SceneManager.ScenesLoaded.Add(scene.handle, scene); } -#if NGO_DAMODE // In distributed authority we need to check if this scene is already added if (networkManager.DistributedAuthorityMode) { @@ -425,7 +424,6 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan } return; } -#endif networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); } } From 18b389d750be99b450130de9953a5b57fe174c7f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 20:05:14 -0500 Subject: [PATCH 013/236] test fix Fixing an issue with disconnecting a client during the distributed objects test. --- .../DistributedAuthority/DistributeObjectsTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs index 1f0e3f62de..6633ea5f34 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -55,9 +55,8 @@ protected override void OnServerAndClientsCreated() protected override IEnumerator OnServerAndClientsConnected() { - // DANGO-TODO: For now, logging all transfers of ownership due to client connect and disconnect. - m_ServerNetworkManager.SpawnManager.EnableDistributeLogging = true; - m_ServerNetworkManager.ConnectionManager.EnableDistributeLogging = true; + m_ServerNetworkManager.SpawnManager.EnableDistributeLogging = m_EnableVerboseDebug; + m_ServerNetworkManager.ConnectionManager.EnableDistributeLogging = m_EnableVerboseDebug; return base.OnServerAndClientsConnected(); } @@ -143,7 +142,7 @@ private bool ValidateOwnershipTablesMatch() { m_ErrorLog.Clear(); var hostId = m_ServerNetworkManager.LocalClientId; - var expectedEntries = m_ClientNetworkManagers.Count() + 1; + var expectedEntries = m_ClientNetworkManagers.Where((c) => c.IsListening && c.IsConnectedClient).Count() + 1; // Make sure all clients have an table created if (DistributeObjectsTestHelper.DistributedObjects.Count < expectedEntries) { @@ -314,12 +313,13 @@ public IEnumerator DistributeNetworkObjects() DistributeObjectsTestHelper.RemoveClient(client.LocalClientId); // Disconnect the client - yield return StopOneClient(client); + yield return StopOneClient(client, true); - yield return new WaitForSeconds(0.3f); + //yield return new WaitForSeconds(0.1f); // Validate all tables match yield return WaitForConditionOrTimeOut(ValidateOwnershipTablesMatch); + AssertOnTimeout($"[Client-{j + 1}][OnwershipTable Mismatch] {m_ErrorLog}"); // When ownership changes, the new owner will randomly pick a new target to move towards and will move towards the target. @@ -346,7 +346,7 @@ private void DisplayOwnership() m_ErrorLog.AppendLine($"[Client-{entry.Key}][Owned Objects: {entry.Value.Count}]"); } - Debug.Log($"{m_ErrorLog}"); + VerboseDebug($"{m_ErrorLog}"); } /// From 41dd028cd7a5c336d5f11cc26a8af62a57387af7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 20:06:06 -0500 Subject: [PATCH 014/236] fix fixing an issue with client disconnect --- .../DistributedAuthority/DistributeObjectsTests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs index 23b34906c7..1db8669d63 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -55,8 +55,8 @@ protected override void OnServerAndClientsCreated() protected override IEnumerator OnServerAndClientsConnected() { // DANGO-TODO: For now, logging all transfers of ownership due to client connect and disconnect. - m_ServerNetworkManager.SpawnManager.EnableDistributeLogging = true; - m_ServerNetworkManager.ConnectionManager.EnableDistributeLogging = true; + m_ServerNetworkManager.SpawnManager.EnableDistributeLogging = m_EnableVerboseDebug; + m_ServerNetworkManager.ConnectionManager.EnableDistributeLogging = m_EnableVerboseDebug; return base.OnServerAndClientsConnected(); } @@ -142,7 +142,7 @@ private bool ValidateOwnershipTablesMatch() { m_ErrorLog.Clear(); var hostId = m_ServerNetworkManager.LocalClientId; - var expectedEntries = m_ClientNetworkManagers.Count() + 1; + var expectedEntries = m_ClientNetworkManagers.Where((c)=> c.IsListening && c.IsConnectedClient).Count() + 1; // Make sure all clients have an table created if (DistributeObjectsTestHelper.DistributedObjects.Count < expectedEntries) { @@ -313,12 +313,13 @@ public IEnumerator DistributeNetworkObjects() DistributeObjectsTestHelper.RemoveClient(client.LocalClientId); // Disconnect the client - yield return StopOneClient(client); + yield return StopOneClient(client, true); - yield return new WaitForSeconds(0.3f); + //yield return new WaitForSeconds(0.1f); // Validate all tables match yield return WaitForConditionOrTimeOut(ValidateOwnershipTablesMatch); + AssertOnTimeout($"[Client-{j + 1}][OnwershipTable Mismatch] {m_ErrorLog}"); // When ownership changes, the new owner will randomly pick a new target to move towards and will move towards the target. @@ -345,7 +346,7 @@ private void DisplayOwnership() m_ErrorLog.AppendLine($"[Client-{entry.Key}][Owned Objects: {entry.Value.Count}]"); } - Debug.Log($"{m_ErrorLog}"); + VerboseDebug($"{m_ErrorLog}"); } /// From a2ccf77fa7dfda58dd2cc0a4da379fe6b7798eb8 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 25 Mar 2024 20:09:05 -0500 Subject: [PATCH 015/236] Update CHANGELOG.md Fixing invalid entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8052e25506..c45db7c11e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -43,7 +43,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) - Fixed issue where a client disconnected by a server-host would not receive a local notification. (#2789) - Fixed issue where a server-host could shutdown during a relay connection but periodically the transport disconnect message sent to any connected clients could be dropped. (#2789) - Fixed issue where a host could disconnect its local client but remain running as a server. (#2789) From 0e04678499d367751f0a24f0a048bf3d60520906 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 20:47:29 -0500 Subject: [PATCH 016/236] test fix Fixing some issues with the time integration test. --- .../Runtime/Timing/TimeIntegrationTest.cs | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs index a04430d4b1..1c83c49498 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Linq; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -13,7 +12,7 @@ namespace Unity.Netcode.RuntimeTests /// public class TimeIntegrationTest : NetcodeIntegrationTest { - private const double k_AdditionalTimeTolerance = 0.3d; // magic number and in theory not needed but without this mac os test fail in Yamato because it looks like we get random framerate drops during unit test. + private const double k_AdditionalTimeTolerance = 0.3333d; // magic number and in theory not needed but without this mac os test fail in Yamato because it looks like we get random framerate drops during unit test. private NetworkTimeState m_ServerState; private NetworkTimeState m_Client1State; @@ -26,17 +25,11 @@ protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() return NetworkManagerInstatiationMode.DoNotCreate; } - private void UpdateTimeStates(NetworkManager[] networkManagers) + private void UpdateTimeStates() { - var server = networkManagers.First(t => t.IsServer); - var firstClient = networkManagers.First(t => t.IsClient); - var secondClient = networkManagers.Last(t => t.IsClient); - - Assert.AreNotEqual(firstClient, secondClient); - - m_ServerState = new NetworkTimeState(server); - m_Client1State = new NetworkTimeState(firstClient); - m_Client2State = new NetworkTimeState(secondClient); + m_ServerState = new NetworkTimeState(m_ServerNetworkManager); + m_Client1State = new NetworkTimeState(m_ClientNetworkManagers[0]); + m_Client2State = new NetworkTimeState(m_ClientNetworkManagers[1]); } [UnityTest] @@ -61,26 +54,21 @@ public IEnumerator TestTimeIntegrationTest(int targetFrameRate, uint tickRate) double frameInterval = 1d / targetFrameRate; double tickInterval = 1d / tickRate; - var networkManagers = NetcodeIntegrationTestHelpers.NetworkManagerInstances.ToArray(); - - var server = networkManagers.First(t => t.IsServer); - var firstClient = networkManagers.First(t => !t.IsServer); - var secondClient = networkManagers.Last(t => !t.IsServer); - - Assert.AreNotEqual(firstClient, secondClient); + var server = m_ServerNetworkManager; + var firstClient = m_ClientNetworkManagers[0]; + var secondClient = m_ClientNetworkManagers[1]; // increase the buffer time of client 2 // the values for client 1 are 0.0333/0.0333 here secondClient.NetworkTimeSystem.LocalBufferSec = 0.2; secondClient.NetworkTimeSystem.ServerBufferSec = 0.1; - UpdateTimeStates(networkManagers); - - - // wait for at least one tick to pass - yield return new WaitUntil(() => m_ServerState.LocalTime.Tick != server.NetworkTickSystem.LocalTime.Tick); - yield return new WaitUntil(() => m_Client1State.LocalTime.Tick != firstClient.NetworkTickSystem.LocalTime.Tick); - yield return new WaitUntil(() => m_Client2State.LocalTime.Tick != secondClient.NetworkTickSystem.LocalTime.Tick); + UpdateTimeStates(); + // wait for a few ticks to pass + for (int i = 0; i < 4; i++) + { + yield return s_DefaultWaitForTick; + } var framesToRun = 3d / frameInterval; @@ -103,7 +91,7 @@ public IEnumerator TestTimeIntegrationTest(int targetFrameRate, uint tickRate) currentAdjustment += additionalTimeTolerance * fpsAdjustment; } - UpdateTimeStates(networkManagers); + UpdateTimeStates(); // compares whether client times have the correct offset to server m_ServerState.AssertCheckDifference(m_Client1State, tickInterval, tickInterval, tickInterval * 2 + frameInterval * 2 + currentAdjustment); From a3b503ba60b3f0c9cdaa5a70d7b7bae74dbe6192 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 20:50:48 -0500 Subject: [PATCH 017/236] update removing the ServerRpc and ClientRpc distributed authority support. Distributed authority only supports universal RPCs. --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 4 - .../Runtime/Core/NetworkBehaviour.cs | 288 ++---------------- 2 files changed, 20 insertions(+), 272 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 2670c171f0..974b492e6c 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -2654,9 +2654,6 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Execute)); instructions.Add(processor.Create(OpCodes.Ceq)); -#if NGO_DAMODE - instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); -#else instructions.Add(processor.Create(OpCodes.Brfalse, returnInstr)); // if (networkManager.IsServer || networkManager.IsHost) -> ServerRpc @@ -2667,7 +2664,6 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); -#endif instructions.Add(returnInstr); instructions.Add(lastInstr); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index c2c4ce4f47..3e0819f728 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -#if NGO_DAMODE using System.Linq; -#endif using Unity.Collections; using UnityEngine; @@ -103,12 +101,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth var rpcWriteSize = 0; // Authority just no ops and sends to itself // Client-Server: Only the server-host sends to self - // DA Mode: Only the owner sends to self -#if NGO_DAMODE - if (networkManager.DistributedAuthorityMode && IsOwner || !networkManager.DistributedAuthorityMode && IsServer) -#else if (IsServer) -#endif { using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp); var context = new NetworkContext @@ -128,34 +121,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } else { -#if NGO_DAMODE - // If not connected to the CMB state service - if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) - { - // DAHost sends directly to the owner - if (networkManager.DAHost || !networkManager.DAHost && NetworkManager.ServerClientId == OwnerClientId) - { - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref serverRpcMessage, networkDelivery, OwnerClientId); - } - else - { - // Clients forward the message to the DAHost who forwards it to the owner - var forwardServerRpc = new ForwardServerRpcMessage() - { - OwnerId = OwnerClientId, - NetworkDelivery = networkDelivery, - ServerRpcMessage = serverRpcMessage, - }; - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardServerRpc, networkDelivery, NetworkManager.ServerClientId); - } - } - else // Client-Server or CMB Service mode: Send it to the server or service (service forwards to the owner) - { - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId); - } -#else rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId); -#endif } bufferWriter.Dispose(); @@ -186,15 +152,6 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth #pragma warning restore IDE1006 // restore naming rule violation check { var networkManager = NetworkManager; -#if NGO_DAMODE - // When in distributed authority mode, owners are only allowed to send to clients (unless in mock mode with a DAHost) - if (networkManager.DistributedAuthorityMode && networkManager.LocalClientId != OwnerClientId && !networkManager.DAHost) - { - NetworkLog.LogError($"Client-{networkManager.LocalClientId} Does not currently own {gameObject.name} (Current Owner: {OwnerClientId})! In distributed authority mode, only the owners of {nameof(NetworkObject)}s can send ClientRpc messages!"); - bufferWriter.Dispose(); - return; - } -#endif var clientRpcMessage = new ClientRpcMessage { Metadata = new RpcMetadata @@ -229,190 +186,40 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth bool shouldInvokeLocally = false; if (clientRpcParams.Send.TargetClientIds != null) { -#if NGO_DAMODE - if (networkManager.DistributedAuthorityMode) - { - foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) - { - if ((networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIds.Contains(OwnerClientId)) - || (!networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIds.Contains(NetworkManager.ServerClientId))) - { - shouldInvokeLocally = true; - continue; - } - // Check to make sure we are sending to only observers, if not log an error. - if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) - { - NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); - } - } - } - else -#endif - { - foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) - { - if (targetClientId == NetworkManager.ServerClientId) - { - shouldInvokeLocally = true; - continue; - } - // Check to make sure we are sending to only observers, if not log an error. - if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) - { - NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); - } - } - } - -#if NGO_DAMODE - // For now, if running as the DAHost or in client-server mode, then send the message to the targeted client identifiers - if (networkManager.DAHost || !networkManager.DistributedAuthorityMode && networkManager.IsServer) + foreach (var targetClientId in clientRpcParams.Send.TargetClientIds) { - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); - } - else - { - // Clients sending to other clients when in mock DAHost mode need to have the DAHost forward the message via ForwardOwnerClientRpc - if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) + if (targetClientId == NetworkManager.ServerClientId) { - // Clients forward the message to the DAHost who forwards it to the owner - var forwardClientRpc = new ForwardClientRpcMessage() - { - TargetClientIds = clientRpcParams.Send.TargetClientIds.ToArray(), - NetworkDelivery = networkDelivery, - ClientRpcMessage = clientRpcMessage, - }; - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardClientRpc, networkDelivery, NetworkManager.ServerClientId); + shouldInvokeLocally = true; + continue; } - else // If CMB service connection, then just send to the server (server address) - if (networkManager.CMBServiceConnection) + // Check to make sure we are sending to only observers, if not log an error. + if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) { - - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); + NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); } } -#else rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); -#endif } else if (clientRpcParams.Send.TargetClientIdsNativeArray != null) { -#if NGO_DAMODE - if (networkManager.DistributedAuthorityMode) + foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) { - foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) + if (targetClientId == NetworkManager.ServerClientId) { - if ((networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIdsNativeArray.Contains(OwnerClientId)) - || (!networkManager.CMBServiceConnection && clientRpcParams.Send.TargetClientIdsNativeArray.Contains(NetworkManager.ServerClientId))) - { - shouldInvokeLocally = true; - continue; - } - // Check to make sure we are sending to only observers, if not log an error. - if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) - { - NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); - } - } - } - else -#endif - { - foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray) - { - if (targetClientId == NetworkManager.ServerClientId) - { - shouldInvokeLocally = true; - continue; - } - // Check to make sure we are sending to only observers, if not log an error. - if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) - { - NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); - } - } - } - -#if NGO_DAMODE - // For now, if running as the DAHost or in client-server mode, then send the message to the targeted client identifiers - if (networkManager.DAHost || !networkManager.DistributedAuthorityMode && networkManager.IsServer) - { - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value); - } - else - { - // Clients sending to other clients when in mock DAHost mode need to have the DAHost forward the message via ForwardOwnerClientRpc - if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) - { - // Clients forward the message to the DAHost who forwards it to the owner - var forwardClientRpc = new ForwardClientRpcMessage() - { - TargetClientIds = clientRpcParams.Send.TargetClientIdsNativeArray.Value.ToArray(), - NetworkDelivery = networkDelivery, - ClientRpcMessage = clientRpcMessage, - }; - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardClientRpc, networkDelivery, NetworkManager.ServerClientId); + shouldInvokeLocally = true; + continue; } - else // If CMB service connection, then just send to the server (server address) - if (networkManager.CMBServiceConnection) + // Check to make sure we are sending to only observers, if not log an error. + if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId)) { - // DANGO-TODO: Determine a way to communicate specific client identifiers to the CMB service. - rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); + NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); } } -#else rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value); -#endif } else { -#if NGO_DAMODE - // For now, if running as the DAHost or in client-server mode, then send the message to the targeted client identifiers - if (networkManager.DAHost || !networkManager.DistributedAuthorityMode && networkManager.IsServer) - { - var observerEnumerator = NetworkObject.Observers.GetEnumerator(); - while (observerEnumerator.MoveNext()) - { - // Skip over the host - if (IsHost && observerEnumerator.Current == NetworkManager.LocalClientId) - { - shouldInvokeLocally = true; - continue; - } - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current); - } - } - else - { - // Clients sending to other clients when in mock DAHost mode need to have the DAHost forward the message via ForwardOwnerClientRpc - if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection) - { - if (NetworkObject.Observers.Count > 1) - { - var remoteClients = NetworkObject.Observers.Where((c) => c != NetworkManager.ServerClientId); - - // Clients forward the message to the DAHost who forwards it to the owner - var forwardClientRpc = new ForwardClientRpcMessage() - { - TargetClientIds = remoteClients.ToArray(), - NetworkDelivery = networkDelivery, - ClientRpcMessage = clientRpcMessage, - }; - // Send the forward message - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref forwardClientRpc, networkDelivery, NetworkManager.ServerClientId); - } - // Send the message to the DAHost directly - rpcWriteSize += networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); - } - else // If CMB service connection, then just send to the server (server address) - if (networkManager.CMBServiceConnection) - { - // DANGO-TODO: Determine a way to communicate specific client identifiers to the CMB service. - rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ServerClientId); - } - } -#else var observerEnumerator = NetworkObject.Observers.GetEnumerator(); while (observerEnumerator.MoveNext()) { @@ -424,7 +231,6 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } rpcWriteSize = NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current); } -#endif } // If we are a server/host then we just no op and send to ourself @@ -565,14 +371,12 @@ internal void __endSendRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, case SendTo.ClientsAndHost: rpcParams.Send.Target = RpcTarget.ClientsAndHost; break; -#if NGO_DAMODE case SendTo.Authority: rpcParams.Send.Target = RpcTarget.Authority; break; case SendTo.NotAuthority: rpcParams.Send.Target = RpcTarget.NotAuthority; break; -#endif case SendTo.SpecifiedInParams: throw new RpcException("This method requires a runtime-specified send target."); } @@ -657,11 +461,13 @@ public NetworkManager NetworkManager /// public bool IsServer { get; private set; } -#if NGO_DAMODE - + /// + /// Determines if the local client has authority over the associated NetworkObject + /// Client-Server: This will return true if IsServer or IsHost + /// Distributed Authority: This will return true if IsOwner + /// public bool HasAuthority { get; internal set; } - internal NetworkClient LocalClient { get; private set; } /// @@ -679,7 +485,6 @@ public bool IsSessionOwner return LocalClient.IsSessionOwner; } } -#endif /// /// Gets if the server (local or remote) is a host - i.e., also a client @@ -831,10 +636,8 @@ internal void UpdateNetworkProperties() IsHost = networkManager.IsListening && networkManager.IsHost; IsClient = networkManager.IsListening && networkManager.IsClient; IsServer = networkManager.IsListening && networkManager.IsServer; -#if NGO_DAMODE LocalClient = networkManager.LocalClient; HasAuthority = networkObject.HasAuthority; -#endif ServerIsHost = networkManager.IsListening && networkManager.ServerIsHost; } } @@ -843,14 +646,11 @@ internal void UpdateNetworkProperties() OwnerClientId = NetworkObjectId = default; IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = ServerIsHost = default; NetworkBehaviourId = default; -#if NGO_DAMODE LocalClient = default; HasAuthority = default; -#endif } } -#if NGO_DAMODE /// /// Distributed Authority Mode Only /// Invoked only on the authority instance when a is deferring its despawn on non-authoritative instances. @@ -860,7 +660,6 @@ internal void UpdateNetworkProperties() /// /// the future network tick that the will be despawned on non-authoritative instances public virtual void OnDeferringDespawn(int despawnTick) { } -#endif /// /// Gets called when the gets spawned, message handlers are ready to be registered and the network is setup. @@ -892,11 +691,7 @@ internal void VisibleOnNetworkSpawn() InitializeVariables(); -#if NGO_DAMODE if (NetworkObject.HasAuthority) -#else - if (IsServer) -#endif { // Since we just spawned the object and since user code might have modified their NetworkVariable, esp. // NetworkList, we need to mark the object as free of updates. @@ -1122,7 +917,6 @@ internal void NetworkVariableUpdate(ulong targetClientId) break; } } -#if NGO_DAMODE // All of this is just to prevent the DA Host from re-sending a NetworkVariable update it received from the client owner // If this NetworkManager is running as a DAHost: // - Only when the write permissions is owner (to pass existing integration tests running as DAHost) @@ -1135,7 +929,7 @@ internal void NetworkVariableUpdate(ulong targetClientId) { shouldSend = false; } -#endif + if (!shouldSend) { continue; @@ -1203,19 +997,16 @@ internal void MarkVariablesDirty(bool dirty) internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) { var networkManager = NetworkManager; -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { writer.WriteValueSafe((ushort)NetworkVariableFields.Count); } -#endif if (NetworkVariableFields.Count == 0) { return; } -#if NGO_DAMODE // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. // Worth either merging or more cleanly separating these codepaths. for (int j = 0; j < NetworkVariableFields.Count; j++) @@ -1258,39 +1049,6 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie } } } -#else - for (int j = 0; j < NetworkVariableFields.Count; j++) - { - if (NetworkVariableFields[j].CanClientRead(targetClientId)) - { - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - var writePos = writer.Position; - // Note: This value can't be packed because we don't know how large it will be in advance - // we reserve space for it, then write the data, then come back and fill in the space - // to pack here, we'd have to write data to a temporary buffer and copy it in - which - // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... - // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. - writer.WriteValueSafe((ushort)0); - var startPos = writer.Position; - NetworkVariableFields[j].WriteField(writer); - var size = writer.Position - startPos; - writer.Seek(writePos); - writer.WriteValueSafe((ushort)size); - writer.Seek(startPos + size); - } - else - { - NetworkVariableFields[j].WriteField(writer); - } - } - else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip - if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - writer.WriteValueSafe((ushort)0); - } - } -#endif } /// @@ -1304,7 +1062,6 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { var networkManager = NetworkManager; -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { reader.ReadValueSafe(out ushort variableCount); @@ -1314,7 +1071,6 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) return; } } -#endif if (NetworkVariableFields.Count == 0) { @@ -1339,7 +1095,6 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) else // If the client cannot read this field, then skip it if (!NetworkVariableFields[j].CanClientRead(clientId)) { -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { reader.ReadValueSafe(out ushort size); @@ -1348,11 +1103,9 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) Debug.LogError("Expected zero size"); } } -#endif continue; } -#if NGO_DAMODE if (networkManager.DistributedAuthorityMode) { // Explicit setting of the NetworkVariableType is only needed for CMB Runtime @@ -1366,7 +1119,6 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) } } else -#endif { NetworkVariableFields[j].ReadField(reader); } From 54ed8bf8b8b94fd46a44f46ed98a847911b2da59 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 20:56:31 -0500 Subject: [PATCH 018/236] update removing NGO_DAMODE --- testproject/ProjectSettings/ProjectSettings.asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index f2d38f6dd6..9fdad33958 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -580,7 +580,7 @@ PlayerSettings: webGLThreadsSupport: 0 webGLDecompressionFallback: 0 scriptingDefineSymbols: - 1: NGO_DAMODE;UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + 1: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT additionalCompilerArguments: 1: - -warnaserror From 3569d80d5ec907e411cb24bb82ffaa6fb982817d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 23:03:28 -0500 Subject: [PATCH 019/236] fix Wrapping rigidbody specific code blocks within COM_UNITY_MODULES_PHYSICS --- .../Components/NetworkTransform.cs | 39 +++++++++++++++++++ .../RigidbodyContactEventManager.cs | 2 + 2 files changed, 41 insertions(+) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 6e5dfc856a..9e51c75fdc 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1337,6 +1337,8 @@ internal NetworkTransformState LocalAuthoritativeNetworkState private Quaternion m_CurrentRotation; private Vector3 m_TargetRotation; + // DANGO-EXP TODO: ADD Rigidbody2D +#if COM_UNITY_MODULES_PHYSICS private bool m_UseRigidbodyForMotion; private Rigidbody m_RigidbodyInternal; private NetworkRigidbody m_NetworkRigidbody; @@ -1350,6 +1352,7 @@ internal void RegisterRigidbody(NetworkRigidbody networkRigidbody, Rigidbody rig m_RigidbodyInternal = rigidbody; } } +#endif #if DEBUG_NETWORKTRANSFORM || UNITY_INCLUDE_TESTS /// @@ -2107,8 +2110,13 @@ private void ApplyAuthoritativeState() // The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated // at the end of this method and assure that when not interpolating the non-authoritative side // cannot make adjustments to any portions the transform not being synchronized. +#if COM_UNITY_MODULES_PHYSICS var adjustedPosition = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : m_CurrentPosition; var adjustedRotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : m_CurrentRotation; +#else + var adjustedPosition = m_CurrentPosition; + var adjustedRotation = m_CurrentRotation; +#endif var adjustedRotAngles = adjustedRotation.eulerAngles; var adjustedScale = m_CurrentScale; @@ -2234,12 +2242,14 @@ private void ApplyAuthoritativeState() { m_CurrentPosition = adjustedPosition; } +#if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.MovePosition(m_CurrentPosition); transform.position = m_RigidbodyInternal.position; } else +#endif { if (InLocalSpace) { @@ -2261,12 +2271,14 @@ private void ApplyAuthoritativeState() m_CurrentRotation = adjustedRotation; } +#if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.MoveRotation(m_CurrentRotation); transform.rotation = m_RigidbodyInternal.rotation; } else +#endif { if (InLocalSpace) { @@ -2381,10 +2393,12 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.position = currentPosition; } +#if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.position = transform.position; } +#endif if (Interpolate) { @@ -2480,11 +2494,13 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.rotation = currentRotation; } +#if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { m_RigidbodyInternal.rotation = transform.rotation; m_RigidbodyInternal.MoveRotation(transform.rotation); } +#endif if (Interpolate) { @@ -2859,8 +2875,13 @@ protected virtual void OnInitialize(ref NetworkVariable r private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; +#if COM_UNITY_MODULES_PHYSICS var position = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : GetSpaceRelativePosition(); var rotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : GetSpaceRelativeRotation(); +#else + var position = GetSpaceRelativePosition(); + var rotation = GetSpaceRelativeRotation(); +#endif UpdatePositionInterpolator(position, serverTime, true); UpdatePositionSlerp(); @@ -2890,6 +2911,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_NetworkTransformTickRegistration = s_NetworkTickRegistration[m_CachedNetworkManager]; } +#if COM_UNITY_MODULES_PHYSICS // Depending upon order of operations, we invoke this in order to assure that proper settings are applied. if (m_NetworkRigidbody) { @@ -2901,6 +2923,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_RigidbodyInternal.position = currentPosition; m_RigidbodyInternal.rotation = currentRotation; } +#endif if (CanCommitToTransform) { @@ -3076,11 +3099,13 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s transform.SetPositionAndRotation(pos, rot); } +#if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion && shouldTeleport) { m_RigidbodyInternal.position = transform.position; m_RigidbodyInternal.rotation = transform.rotation; } +#endif transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; @@ -3166,11 +3191,19 @@ private void UpdateInterpolation() var serverTime = m_CachedNetworkManager.ServerTime; var cachedServerTime = serverTime.Time; var offset = 0f; +#if COM_UNITY_MODULES_PHYSICS var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; +#else + var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; +#endif // With owner authoritative mode, non-authority clients can lag behind // by more than 1 tick period of time. The current "solution" for now // is to make their cachedRenderTime run 2 ticks behind. +#if COM_UNITY_MODULES_PHYSICS var ticksAgo = (!IsServerAuthoritative() && !IsServer) || m_UseRigidbodyForMotion ? 2 : 1; +#else + var ticksAgo = (!IsServerAuthoritative() && !IsServer) ? 2 : 1; +#endif if (m_CachedNetworkManager.DistributedAuthorityMode) { ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); @@ -3209,7 +3242,11 @@ private void UpdateInterpolation() protected virtual void Update() { // If not spawned or this instance has authority, exit early +#if COM_UNITY_MODULES_PHYSICS if (!IsSpawned || CanCommitToTransform || m_UseRigidbodyForMotion) +#else + if (!IsSpawned || CanCommitToTransform) +#endif { return; } @@ -3222,6 +3259,7 @@ protected virtual void Update() } +#if COM_UNITY_MODULES_PHYSICS /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3245,6 +3283,7 @@ internal void OnFixedUpdate() // Apply the current authoritative state ApplyAuthoritativeState(); } +#endif /// /// Override this method and return false to switch to owner authoritative mode diff --git a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs index f14c638bcf..688aa68e97 100644 --- a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs @@ -1,3 +1,4 @@ +#if COM_UNITY_MODULES_PHYSICS using System.Collections.Generic; using Unity.Collections; using Unity.Jobs; @@ -226,3 +227,4 @@ public void Execute(int index) } } } +#endif From 65c0cd6fd90085e281f7ac56181e4a52aba406f7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 23:09:50 -0500 Subject: [PATCH 020/236] style Removing whitespaces. --- .../Runtime/NetworkVariable/NetworkVariable.cs | 2 +- .../Runtime/DistributedAuthority/DistributeObjectsTests.cs | 2 +- .../Tests/Runtime/NetworkVariableTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 6197d750cc..cf4d36edeb 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -81,7 +81,7 @@ public virtual T Value { return; } - + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) { throw new InvalidOperationException($"[Client-{m_NetworkManager.LocalClientId}][{m_NetworkBehaviour.name}][{Name}] Write permissions ({WritePerm}) for this client instance is not allowed!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs index d0e81db4fa..75a1e9cb78 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -141,7 +141,7 @@ private bool ValidateOwnershipTablesMatch() { m_ErrorLog.Clear(); var hostId = m_ServerNetworkManager.LocalClientId; - var expectedEntries = m_ClientNetworkManagers.Where((c)=> c.IsListening && c.IsConnectedClient).Count() + 1; + var expectedEntries = m_ClientNetworkManagers.Where((c) => c.IsListening && c.IsConnectedClient).Count() + 1; // Make sure all clients have an table created if (DistributeObjectsTestHelper.DistributedObjects.Count < expectedEntries) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 7bee2a8acd..4886899e06 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -20,7 +20,7 @@ public static IEnumerable TestDataSource() { // DANGO-EXP TODO: Add support for distributed authority mode if (hostOrServer == HostOrServer.DAHost) - { + { continue; } yield return new TestFixtureData(hostOrServer); From 916e637203da71999af473e44002896a61195c9d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 23:16:24 -0500 Subject: [PATCH 021/236] style more whitespace removal --- .../TestHelpers/Runtime/MockTimeProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs index 46ac3db8a1..daf2709a38 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTimeProvider.cs @@ -6,7 +6,7 @@ public class MockTimeProvider : IRealTimeProvider public float UnscaledTime => (float)s_DoubleRealTime; public float UnscaledDeltaTime => (float)s_DoubleDelta; public float DeltaTime => (float)s_DoubleDelta; - + // DANGO-EXP TODO: Figure out how we want to handle time travel with fixed delta time. public float FixedDeltaTime => (float)s_DoubleDelta; From 5d38c546b001bf291b9fe01f77a2c485d8953e44 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 25 Mar 2024 23:23:44 -0500 Subject: [PATCH 022/236] style removing using directive for System.Linq --- com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 3e0819f728..ce0f648ace 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Unity.Collections; using UnityEngine; From c9df98b6f7646575e069220ffce2ca5af7692077 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 26 Mar 2024 13:21:34 -0500 Subject: [PATCH 023/236] test Excluding certain tests from the tools integration test project tests. Increasing the primary test project's 2023 editor to 2023.2 as opposed to 2023.1. --- .yamato/project.metafile | 2 +- .../Tests/Editor/Build/BuildTests.cs | 2 ++ .../NetworkTransform/NetworkTransformStateTests.cs | 2 ++ .../Runtime/NetworkTransform/NetworkTransformTests.cs | 10 +++++++++- .../Tests/Runtime/NetworkVariableTests.cs | 6 ++++++ .../Tests/Runtime/Timing/TimeIntegrationTest.cs | 2 ++ .../Tests/Runtime/TransformInterpolationTests.cs | 2 ++ .../Tests/Runtime/UniversalRpcTests.cs | 2 ++ 8 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index c4df701032..6a840717ff 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -43,7 +43,7 @@ projects: path: com.unity.netcode.gameobjects test_editors: - 2022.3 - - 2023.1 + - 2023.2 - trunk - name: minimalproject path: minimalproject diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs index c8cde89618..bbb24a3d3b 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs @@ -1,3 +1,4 @@ +#if !MULTIPLAYER_TOOLS using System.IO; using System.Reflection; using NUnit.Framework; @@ -38,3 +39,4 @@ public void BasicBuildTest() } } } +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 14bdcc7c4a..6cbc832995 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -1,3 +1,4 @@ +#if !MULTIPLAYER_TOOLS using NUnit.Framework; using Unity.Netcode.Components; using UnityEngine; @@ -907,3 +908,4 @@ public void TestThresholds( } } } +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 65e16bdb03..396733193e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -10,23 +10,29 @@ namespace Unity.Netcode.RuntimeTests /// models for each operating mode. /// [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] +#if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] +#endif [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] +#if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] +#endif [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] +#if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] +#endif public class NetworkTransformTests : NetworkTransformBase { protected const int k_TickRate = 60; @@ -94,6 +100,8 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTran } } +// Reducing tools integration test footprint +#if !MULTIPLAYER_TOOLS /// /// Validates that transform values remain the same when a NetworkTransform is /// parented under another NetworkTransform under all of the possible axial conditions @@ -379,7 +387,7 @@ public void LateJoiningPlayerInitialScaleValues([Values] TransformSpace testLoca } } } - +#endif /// /// Tests changing all axial values one at a time. /// These tests are performed: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 4886899e06..a85f344218 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -431,7 +431,10 @@ public override void OnNetworkSpawn() } } + +#if !MULTIPLAYER_TOOLS [TestFixture(true)] +#endif [TestFixture(false)] public class NetworkVariableTests : NetcodeIntegrationTest { @@ -542,6 +545,7 @@ private void InitializeServerAndClients(HostOrServer useHost) TimeTravelToNextTick(); } +#if !MULTIPLAYER_TOOLS /// /// Runs generalized tests on all predefined NetworkVariable types /// @@ -960,6 +964,7 @@ public void TestINetworkSerializableClassCallsNetworkSerialize([Values] HostOrSe // Wait for the client-side to notify it is finished initializing and spawning. Assert.True(WaitForConditionOrTimeOutWithTimeTravel(VerifyCallback)); } +#endif [Test] public void TestINetworkSerializableStructCallsNetworkSerialize([Values] HostOrServer useHost) @@ -5102,6 +5107,7 @@ protected override IEnumerator OnTearDown() } } + /// /// Handles the more generic conditional logic for NetworkList tests /// which can be used with the diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs index 1c83c49498..e019bb2137 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs @@ -1,3 +1,4 @@ +#if !MULTIPLAYER_TOOLS using System; using System.Collections; using NUnit.Framework; @@ -199,3 +200,4 @@ public override int GetHashCode() } } } +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 85305970e0..ac64d2c1d1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -1,3 +1,4 @@ +#if !MULTIPLAYER_TOOLS using System.Collections; using NUnit.Framework; using Unity.Netcode.Components; @@ -226,3 +227,4 @@ public IEnumerator TransformInterpolationTest() } } } +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs index d163213200..ed4cee0d23 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -1,3 +1,4 @@ +#if !MULTIPLAYER_TOOLS using System; using System.Collections.Generic; using System.Diagnostics; @@ -2128,3 +2129,4 @@ public void TestRpcTargetUseNotSingle() } } +#endif From 6132bb9d23454b97ec71e64779a77f74057a7e50 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 26 Mar 2024 16:06:56 -0500 Subject: [PATCH 024/236] test Adjusting some of the metrics tests to yield more accurate end results. --- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- .../Metrics/NetworkObjectMetricsTests.cs | 55 ++++++++++++++----- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 3829d498ea..57eb8e0374 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1432,7 +1432,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } } } - else // DANGO-TODO: If we not the server, distributed authority mode is enabled, and we are the owner then inform the DAHost to despawn the NetworkObject + 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 diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs index 1679607d23..dce18644f3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs @@ -73,20 +73,18 @@ public IEnumerator TrackNetworkObjectSpawnReceivedMetric() [UnityTest] public IEnumerator TrackNetworkObjectDestroySentMetric() { - var networkObject = SpawnNetworkObject(); - - yield return s_DefaultWaitForTick; - var waitForMetricEvent = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedSent); + var networkObject = SpawnNetworkObject(); var objectName = networkObject.name; Server.SpawnManager.OnDespawnObject(networkObject, true); - yield return s_DefaultWaitForTick; + // Wait for the metric to be received yield return waitForMetricEvent.WaitForMetricsReceived(); - var objectDestroyedSentMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound(); - Assert.AreEqual(2, objectDestroyedSentMetricValues.Count); + + // The server should have sent 1 destroy message to the 1 client connected + Assert.AreEqual(1, objectDestroyedSentMetricValues.Count); var objectDestroyed = objectDestroyedSentMetricValues.Last(); Assert.AreEqual(Client.LocalClientId, objectDestroyed.Connection.Id); @@ -242,20 +240,47 @@ public IEnumerator TrackNetworkObjectCountAfterDespawnOnServer() [UnityTest] public IEnumerator TrackNetworkObjectCountAfterDespawnOnClient() { - var objectList = Server.SpawnManager.SpawnedObjectsList; - for (int i = objectList.Count - 1; i >= 0; --i) + var initialSpawnCount = Server.SpawnManager.SpawnedObjectsList.Count; + var spawnedObjects = new List(); + //By default, we have 2 network objects and will have spawned 4 so we want to wait for metrics to tell us we have 6 spawned objects + var waitForGaugeValues = new WaitForGaugeMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.NetworkObjects, metric => (int)metric == 6); + + for (int i = 0; i < 4; i++) { - objectList.ElementAt(i).Despawn(); + spawnedObjects.Add(SpawnObject(m_NewNetworkPrefab, Server)); } - //By default, we have 2 network objects - //There's a slight delay between the spawn on the server and the spawn on the client - //We want to have metrics when the value is different than the 2 default one to confirm the client has the new value - var waitForGaugeValues = new WaitForGaugeMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.NetworkObjects, metric => (int)metric != 2); + yield return waitForGaugeValues.WaitForMetricsReceived(); + var value = waitForGaugeValues.AssertMetricValueHaveBeenFound(); + Assert.AreEqual(6, value); + // Create a new gauge that waits for the initial spawned client count + waitForGaugeValues = new WaitForGaugeMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.NetworkObjects, metric => (int)metric != 6); + + // Now despawn the 4 spawned objects + foreach (var spawnedObject in spawnedObjects) + { + spawnedObject.GetComponent().Despawn(); + } + spawnedObjects.Clear(); yield return waitForGaugeValues.WaitForMetricsReceived(); + value = waitForGaugeValues.AssertMetricValueHaveBeenFound(); - var value = waitForGaugeValues.AssertMetricValueHaveBeenFound(); + // Validate the value is equals to the spawned objects prior to spawning the network prefab instances + Assert.AreEqual(initialSpawnCount, value); + + // Create a new gauge that waits for the initial spawned client count + waitForGaugeValues = new WaitForGaugeMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.NetworkObjects, metric => (int)metric == 0); + + // Now assure despawning players are being tracked too + var spawnedPlayers = Server.SpawnManager.SpawnedObjectsList.ToList(); + foreach (var spawnedObject in spawnedPlayers) + { + spawnedObject.Despawn(); + } + yield return waitForGaugeValues.WaitForMetricsReceived(); + value = waitForGaugeValues.AssertMetricValueHaveBeenFound(); + // Nothing should be spawned on the client at this point Assert.AreEqual(0, value); } #endif From 959e887b20bd73fa07ceb7db35e20affc46a450f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 26 Mar 2024 17:36:59 -0500 Subject: [PATCH 025/236] style removing white spaces. --- .../Tests/Runtime/NetworkTransform/NetworkTransformTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 396733193e..abaac55050 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -100,7 +100,6 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTran } } -// Reducing tools integration test footprint #if !MULTIPLAYER_TOOLS /// /// Validates that transform values remain the same when a NetworkTransform is @@ -388,6 +387,7 @@ public void LateJoiningPlayerInitialScaleValues([Values] TransformSpace testLoca } } #endif + /// /// Tests changing all axial values one at a time. /// These tests are performed: From dbcb3475d73dd0a83144134d1818e3a07e894d95 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 26 Mar 2024 21:43:20 -0500 Subject: [PATCH 026/236] update excluding certain tests from the minimal project pass. minor fix for initial in-scene NetworkObject spawning until service fully consumes the initial Synchronize event. --- .../Runtime/Spawning/NetworkSpawnManager.cs | 13 ++++++++++++- .../Tests/Runtime/NetworkVariableTests.cs | 2 ++ .../Runtime/NetworkVariableTestsHelperTypes.cs | 3 ++- .../Tests/Runtime/UniversalRpcTests.cs | 2 +- .../ProjectSettings/ProjectSettings.asset | 3 ++- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 57eb8e0374..a804c4d231 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1306,6 +1306,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep() #else var networkObjects = UnityEngine.Object.FindObjectsOfType(); #endif + var isConnectedCMBService = NetworkManager.CMBServiceConnection; for (int i = 0; i < networkObjects.Length; i++) { if (networkObjects[i].NetworkManager == NetworkManager) @@ -1320,7 +1321,17 @@ internal void ServerSpawnSceneObjectsOnStartSweep() { ownerId = NetworkManager.LocalClientId; } - SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true); + // DANGO-EXP TODO: Once the service consumes the initial SceneEventType.Synchronize sent to it, + // we should remove this. + if (isConnectedCMBService) + { + networkObjects[i].IsSceneObject = true; + networkObjects[i].Spawn(true); + } + else + { + SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true); + } } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index a85f344218..eed032e296 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -1,3 +1,4 @@ +#if !NGO_MINIMALPROJECT using System; using System.Collections; using System.Collections.Generic; @@ -5486,3 +5487,4 @@ public IEnumerator ModifyNetworkVariableOrListOnNetworkDespawn() } } } +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs index 67d54cd20d..6f2752c03b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs @@ -896,7 +896,7 @@ public class IntermediateNetworkBehavior : TemplateNetworkBehaviourType { public NetworkVariable TheVar2; } - +#if !NGO_MINIMALPROJECT public class ClassHavingNetworkBehaviour : IntermediateNetworkBehavior { @@ -912,6 +912,7 @@ public class StructHavingNetworkBehaviour : TemplateNetworkBehaviourType, INetworkSerializeByMemcpy { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs index ed4cee0d23..a756bd7996 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -1,4 +1,4 @@ -#if !MULTIPLAYER_TOOLS +#if !MULTIPLAYER_TOOLS && !NGO_MINIMALPROJECT using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/minimalproject/ProjectSettings/ProjectSettings.asset b/minimalproject/ProjectSettings/ProjectSettings.asset index e56e5a90f9..d110b4845a 100644 --- a/minimalproject/ProjectSettings/ProjectSettings.asset +++ b/minimalproject/ProjectSettings/ProjectSettings.asset @@ -578,7 +578,8 @@ PlayerSettings: webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLDecompressionFallback: 0 - scriptingDefineSymbols: {} + scriptingDefineSymbols: + 1: NGO_MINIMALPROJECT additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: {} From ee27047f8f7fbaea10ef34ad1cb09b0100ab9136 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 29 Mar 2024 15:54:25 -0500 Subject: [PATCH 027/236] update & fix Consolidated NetworkRigidbody and NetworkRigidbody2D into single base class that provides more customizable options for users. NetworkObject no longer implements the Update method in order to check for object scene migration. Instead, it is registered in a table that is updated in the late update prior to sending any messages about objects migrating into a new scene. This removes the added processing per NetworkObject for scenarios where the NetworkObject plays a more passive role (i.e. a sinlge NetworkBehaviour with one or more NetworkVariables). Fixed issue with parenting when using Rigidbody motion in NetworkTransform. Fixed issue with NotMeRpcTarget when in distributed authority mode. --- .../Messages/NetworkTransformMessage.cs | 9 +- .../Components/NetworkRigidBodyBase.cs | 529 ++++++++++++++++++ .../Components/NetworkRigidBodyBase.cs.meta | 11 + .../Components/NetworkRigidbody.cs | 151 +---- .../Components/NetworkRigidbody2D.cs | 70 +-- .../Components/NetworkTransform.cs | 101 ++-- .../Runtime/Core/NetworkManager.cs | 3 + .../Runtime/Core/NetworkObject.cs | 60 +- .../Messaging/RpcTargets/NotMeRpcTarget.cs | 4 +- 9 files changed, 661 insertions(+), 277 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs create mode 100644 com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs index e61760d1c3..be5157b1ab 100644 --- a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs @@ -174,14 +174,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int continue; } - if (clientId > 1) - { - networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); - } - else - { - networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); - } + networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); } // Dispose of the reader used for forwarding currentMessage.m_CurrentReader.Dispose(); diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs new file mode 100644 index 0000000000..c8bf7196c3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs @@ -0,0 +1,529 @@ +#if COM_UNITY_MODULES_PHYSICS +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Unity.Netcode.Components +{ + /// + /// NetworkRigidbodyBase is a unified and integration that helps to synchronize physics motion, collision, and interpolation + /// when used with a . + /// + /// + /// For a customizable netcode Rigidbody, create your own component from this class and use + /// during instantiation (i.e. invoked from within the Awake method). You can re-initialize after having initialized but only when the is not spawned. + /// + public abstract class NetworkRigidbodyBase : NetworkBehaviour + { + /// + /// When enabled, the associated will use the Rigidbody/Rigidbody2D to apply and synchronize changes in position, rotation, and + /// allows for the use of Rigidbody interpolation/extrapolation. + /// + /// + /// If is enabled, non-authoritative instances can only use Rigidbody interpolation. If a network prefab is set to + /// extrapolation and is enabled, then non-authoritative instances will automatically be adjusted to use Rigidbody + /// interpolation while the authoritative instance will still use extrapolation. + /// + public bool UseRigidBodyForMotion; + + /// + /// When enabled (default), automatically set the Kinematic state of the Rigidbody based on ownership. + /// When disabled, Kinematic state needs to be set by external script(s). + /// + public bool AutoUpdateKinematicState = true; + + /// + /// Primarily applies to the property when disabled but you still want + /// the Rigidbody to be automatically set to Kinematic when despawned. + /// + public bool AutoSetKinematicOnDespawn = true; + + // Determines if this is a Rigidbody or Rigidbody2D implementation + private bool m_IsRigidbody2D => RigidbodyType == RigidbodyTypes.Rigidbody2D; + // Used to cache the authority state of this Rigidbody during the last frame + private bool m_IsAuthority; + private Rigidbody m_Rigidbody; + private Rigidbody2D m_Rigidbody2D; + private NetworkTransform m_NetworkTransform; + private enum InterpolationTypes + { + None, + Interpolate, + Extrapolate + } + private InterpolationTypes m_OriginalInterpolation; + + /// + /// Used to define the type of Rigidbody implemented. + /// + /// + public enum RigidbodyTypes + { + Rigidbody, + Rigidbody2D, + } + + public RigidbodyTypes RigidbodyType { get; private set; } + + /// + /// Initializes the networked Rigidbody based on the + /// passed in as a parameter. + /// + /// + /// Cannot be initialized while the associated is spawned. + /// + /// type of rigid body being initialized + /// (optional) The to be used + /// (optional) The to be used + protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform networkTransform = null, Rigidbody2D rigidbody2D = null, Rigidbody rigidbody = null) + { + // Don't initialize if already spawned + if (IsSpawned) + { + Debug.LogError($"[{name}] Attempting to initialize while spawned is not allowed."); + return; + } + RigidbodyType = rigidbodyType; + m_Rigidbody2D = rigidbody2D; + m_Rigidbody = rigidbody; + m_NetworkTransform = networkTransform; + + if (m_IsRigidbody2D && m_Rigidbody2D == null) + { + m_Rigidbody2D = GetComponent(); + + } + else if (m_Rigidbody == null) + { + m_Rigidbody = GetComponent(); + } + + SetOriginalInterpolation(); + + if (m_NetworkTransform == null) + { + m_NetworkTransform = GetComponent(); + } + + if (m_NetworkTransform != null) + { + m_NetworkTransform.RegisterRigidbody(this); + } + else + { + throw new System.Exception($"[Missing {nameof(NetworkTransform)}] No {nameof(NetworkTransform)} is assigned or can be found during initialization!"); + } + + if (AutoUpdateKinematicState) + { + SetIsKinematic(true); + } + } + + /// + /// Gets the position of the Rigidbody + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3 GetPosition() + { + if (m_IsRigidbody2D) + { + return m_Rigidbody2D.position; + } + else + { + return m_Rigidbody.position; + } + } + + /// + /// Gets the rotation of the Rigidbody + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Quaternion GetRotation() + { + if (m_IsRigidbody2D) + { + var quaternion = Quaternion.identity; + var angles = quaternion.eulerAngles; + angles.z = m_Rigidbody2D.rotation; + quaternion.eulerAngles = angles; + return quaternion; + } + else + { + return m_Rigidbody.rotation; + } + } + + /// + /// Moves the rigid body + /// + /// The position to move towards + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MovePosition(Vector3 position) + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.MovePosition(position); + } + else + { + m_Rigidbody.MovePosition(position); + } + } + + /// + /// Directly applies a position (like teleporting) + /// + /// position to apply to the Rigidbody + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetPosition(Vector3 position) + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.position = position; + } + else + { + m_Rigidbody.position = position; + } + } + + /// + /// Applies the rotation and position of the 's + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ApplyCurrentTransform() + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.position = transform.position; + m_Rigidbody2D.rotation = transform.eulerAngles.z; + } + else + { + m_Rigidbody.position = transform.position; + m_Rigidbody.rotation = transform.rotation; + } + } + + /// + /// Rotatates the Rigidbody towards a specified rotation + /// + /// The rotation expressed as a + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MoveRotation(Quaternion rotation) + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.MoveRotation(rotation); + } + else + { + m_Rigidbody.MoveRotation(rotation); + } + } + + /// + /// Applies a rotation to the Rigidbody + /// + /// The rotation to apply expressed as a + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetRotation(Quaternion rotation) + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.rotation = rotation.eulerAngles.z; + } + else + { + m_Rigidbody.rotation = rotation; + } + } + + /// + /// Sets the original interpolation of the Rigidbody while taking the Rigidbody type into consideration + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetOriginalInterpolation() + { + if (m_IsRigidbody2D) + { + switch (m_Rigidbody2D.interpolation) + { + case RigidbodyInterpolation2D.None: + { + m_OriginalInterpolation = InterpolationTypes.None; + break; + } + case RigidbodyInterpolation2D.Interpolate: + { + m_OriginalInterpolation = InterpolationTypes.Interpolate; + break; + } + case RigidbodyInterpolation2D.Extrapolate: + { + m_OriginalInterpolation = InterpolationTypes.Extrapolate; + break; + } + } + } + else + { + switch (m_Rigidbody.interpolation) + { + case RigidbodyInterpolation.None: + { + m_OriginalInterpolation = InterpolationTypes.None; + break; + } + case RigidbodyInterpolation.Interpolate: + { + m_OriginalInterpolation = InterpolationTypes.Interpolate; + break; + } + case RigidbodyInterpolation.Extrapolate: + { + m_OriginalInterpolation = InterpolationTypes.Extrapolate; + break; + } + } + } + } + + /// + /// Wakes the Rigidbody if it is sleeping + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WakeIfSleeping() + { + if (m_IsRigidbody2D) + { + if (m_Rigidbody2D.IsSleeping()) + { + m_Rigidbody2D.WakeUp(); + } + } + else + { + if (m_Rigidbody.IsSleeping()) + { + m_Rigidbody.WakeUp(); + } + } + } + + /// + /// Puts the Rigidbody to sleep + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SleepRigidbody() + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.Sleep(); + } + else + { + m_Rigidbody.Sleep(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsKinematic() + { + if (m_IsRigidbody2D) + { + return m_Rigidbody2D.isKinematic; + } + else + { + return m_Rigidbody.isKinematic; + } + } + + /// + /// Sets the kinematic state of the Rigidbody and handles updating the Rigidbody's + /// interpolation setting based on the Kinematic state. + /// + /// + /// When using the Rigidbody for motion, this automatically + /// adjusts from extrapolation to interpolation if: + /// - The Rigidbody was originally set to extrapolation + /// - The NetworkTransform is set to interpolate + /// When the two above conditions are true: + /// - When switching from non-kinematic to kinematic this will automatically + /// switch the Rigidbody from extrapolation to interpolate. + /// - When switching from kinematic to non-kinematic this will automatically + /// switch the Rigidbody from interpolation back to extrapolation. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetIsKinematic(bool isKinematic) + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.isKinematic = isKinematic; + } + else + { + m_Rigidbody.isKinematic = isKinematic; + } + + if (UseRigidBodyForMotion) + { + // Only if the NetworkTransform is set to interpolate do we need to check for extrapolation + if (m_NetworkTransform.Interpolate && m_OriginalInterpolation == InterpolationTypes.Extrapolate) + { + if (IsKinematic()) + { + // If not already set to interpolate then set the Rigidbody to interpolate + if (m_Rigidbody.interpolation == RigidbodyInterpolation.Extrapolate) + { + // Sleep until the next fixed update when switching from extrapolation to interpolation + SleepRigidbody(); + SetInterpolation(InterpolationTypes.Interpolate); + } + } + else + { + // Switch it back to the original interpolation if non-kinematic (doesn't require sleep). + SetInterpolation(m_OriginalInterpolation); + } + } + } + else + { + SetInterpolation(m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? InterpolationTypes.None : m_OriginalInterpolation)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetInterpolation(InterpolationTypes interpolationType) + { + switch (interpolationType) + { + case InterpolationTypes.None: + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.None; + } + else + { + m_Rigidbody.interpolation = RigidbodyInterpolation.None; + } + break; + } + case InterpolationTypes.Interpolate: + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate; + } + else + { + m_Rigidbody.interpolation = RigidbodyInterpolation.Interpolate; + } + break; + } + case InterpolationTypes.Extrapolate: + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Extrapolate; + } + else + { + m_Rigidbody.interpolation = RigidbodyInterpolation.Extrapolate; + } + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResetInterpolation() + { + SetInterpolation(m_OriginalInterpolation); + } + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + UpdateOwnershipAuthority(); + base.OnOwnershipChanged(previous, current); + } + + /// + /// Sets the authority based on whether it is server or owner authoritative + /// + /// + /// Distributed authority sessions will always be owner authoritative. + /// + internal void UpdateOwnershipAuthority() + { + if (NetworkManager.DistributedAuthorityMode) + { + // When in distributed authority mode, always use HasAuthority + m_IsAuthority = HasAuthority; + } + else + { + if (m_NetworkTransform.IsServerAuthoritative()) + { + m_IsAuthority = NetworkManager.IsServer; + } + else + { + m_IsAuthority = IsOwner; + } + } + + if (AutoUpdateKinematicState) + { + SetIsKinematic(!m_IsAuthority); + } + } + + /// + public override void OnNetworkSpawn() + { + UpdateOwnershipAuthority(); + } + + /// + public override void OnNetworkDespawn() + { + // If we are automatically handling the kinematic state... + if (AutoUpdateKinematicState || AutoSetKinematicOnDespawn) + { + // Turn off physics for the rigid body until spawned, otherwise + // non-owners can run fixed updates before the first full + // NetworkTransform update and physics will be applied (i.e. gravity, etc) + SetIsKinematic(true); + } + SetInterpolation(m_OriginalInterpolation); + } + + /// + /// When is enabled, the will update Kinematic instances using + /// the Rigidbody's move methods allowing Rigidbody interpolation settings to be taken into consideration by the physics simulation. + /// + /// + /// This will update the associated during FixedUpdate which also avoids the added expense of adding + /// a FixedUpdate to all instances where some might not be using a Rigidbody. + /// + private void FixedUpdate() + { + if (!IsSpawned || m_NetworkTransform == null || !UseRigidBodyForMotion) + { + return; + } + m_NetworkTransform.OnFixedUpdate(); + } + } +} +#endif // COM_UNITY_MODULES_PHYSICS + diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs.meta b/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs.meta new file mode 100644 index 0000000000..544ddbab48 --- /dev/null +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c4434f0563fb7f42b3b2993c97ae81a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs index 3ea8ff0487..7c93a5deac 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs @@ -7,159 +7,14 @@ namespace Unity.Netcode.Components /// NetworkRigidbody allows for the use of on network objects. By controlling the kinematic /// mode of the and disabling it on all peers but the authoritative one. /// - [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(NetworkTransform))] + [RequireComponent(typeof(Rigidbody))] [AddComponentMenu("Netcode/Network Rigidbody")] - public class NetworkRigidbody : NetworkBehaviour + public class NetworkRigidbody : NetworkRigidbodyBase { - public bool UseRigidBodyForMotion; - private Rigidbody m_Rigidbody; - private NetworkTransform m_NetworkTransform; - private RigidbodyInterpolation m_OriginalInterpolation; - - // Used to cache the authority state of this Rigidbody during the last frame - private bool m_IsAuthority; - protected virtual void Awake() { - m_NetworkTransform = GetComponent(); - m_Rigidbody = GetComponent(); - SetupRigidbody(); - } - - /// - /// If the current has authority, - /// then use the interpolation strategy, - /// if the is handling interpolation, - /// set interpolation to none on the - ///
- /// Turn off physics for the rigid body until spawned, otherwise - /// clients can run fixed update before the first - /// full update - ///
- private void SetupRigidbody() - { - m_OriginalInterpolation = m_Rigidbody.interpolation; - if (m_NetworkTransform != null) - { - m_NetworkTransform.RegisterRigidbody(this, m_Rigidbody); - } - m_Rigidbody.isKinematic = true; - } - - /// - /// For owner authoritative (i.e. ClientNetworkTransform) - /// we adjust our authority when we gain ownership - /// - public override void OnGainedOwnership() - { - UpdateOwnershipAuthority(); - } - - /// - /// For owner authoritative(i.e. ClientNetworkTransform) - /// we adjust our authority when we have lost ownership - /// - public override void OnLostOwnership() - { - UpdateOwnershipAuthority(); - } - - protected override void OnOwnershipChanged(ulong previous, ulong current) - { - if (NetworkManager.LocalClientId == current || NetworkManager.LocalClientId == previous) - { - UpdateOwnershipAuthority(); - } - base.OnOwnershipChanged(previous, current); - } - - /// - /// Sets the authority differently depending upon - /// whether it is server or owner authoritative - /// - internal void UpdateOwnershipAuthority() - { - if (NetworkManager.DistributedAuthorityMode) - { - // When in distributed authority mode, always use HasAuthority - m_IsAuthority = HasAuthority; - } - else - { - if (m_NetworkTransform.IsServerAuthoritative()) - { - m_IsAuthority = NetworkManager.IsServer; - } - else - { - m_IsAuthority = IsOwner; - } - } - - m_Rigidbody.isKinematic = !m_IsAuthority; - - if (UseRigidBodyForMotion) - { - if (m_Rigidbody.isKinematic) - { - // Since we don't support kinematic extrapolation, if we are transitioning to kinematic mode - // and the user is using extrapolation on the authority side then we switch it to interpolation. - if (m_Rigidbody.interpolation == RigidbodyInterpolation.Extrapolate) - { - // Sleep until the next fixed update when switching - m_Rigidbody.Sleep(); - m_Rigidbody.interpolation = RigidbodyInterpolation.Interpolate; - } - } - else - { - // Switch it back to the original interpolation if non-kinematic (doesn't require sleep). - if (m_Rigidbody.interpolation != m_OriginalInterpolation) - { - m_Rigidbody.interpolation = m_OriginalInterpolation; - } - } - } - else - { - m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation); - } - } - - /// - public override void OnNetworkSpawn() - { - UpdateOwnershipAuthority(); - } - - /// - public override void OnNetworkDespawn() - { - //m_Rigidbody.interpolation = m_OriginalInterpolation; - // Turn off physics for the rigid body until spawned, otherwise - // non-owners can run fixed updates before the first full - // NetworkTransform update and physics will be applied (i.e. gravity, etc) - m_Rigidbody.isKinematic = true; - m_Rigidbody.interpolation = m_OriginalInterpolation; - } - - /// - /// When using with a and is - /// enabled, the will update Kinematic instances using the move methods. - /// This allows one to use interpolation on both the authoritative and nonauthoritative instances. - /// - /// - /// The updates during FixedUpdate to avoid the expense of having - /// a FixedUpdate on instances that do not have a . - /// - private void FixedUpdate() - { - if (!IsSpawned || m_NetworkTransform == null || !UseRigidBodyForMotion) - { - return; - } - m_NetworkTransform.OnFixedUpdate(); + Initialize(RigidbodyTypes.Rigidbody); } } } diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs index 67397f310e..a178660df5 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs @@ -7,76 +7,14 @@ namespace Unity.Netcode.Components /// NetworkRigidbody allows for the use of on network objects. By controlling the kinematic /// mode of the rigidbody and disabling it on all peers but the authoritative one. ///
- [RequireComponent(typeof(Rigidbody2D))] [RequireComponent(typeof(NetworkTransform))] + [RequireComponent(typeof(Rigidbody2D))] [AddComponentMenu("Netcode/Network Rigidbody 2D")] - public class NetworkRigidbody2D : NetworkBehaviour + public class NetworkRigidbody2D : NetworkRigidbodyBase { - private Rigidbody2D m_Rigidbody; - private NetworkTransform m_NetworkTransform; - - private bool m_OriginalKinematic; - private RigidbodyInterpolation2D m_OriginalInterpolation; - - // Used to cache the authority state of this rigidbody during the last frame - private bool m_IsAuthority; - - private void Awake() - { - m_Rigidbody = GetComponent(); - m_NetworkTransform = GetComponent(); - - // Turn off physics for the rigid body until spawned, otherwise - // clients can run fixed update before the first full - // NetworkTransform update - m_Rigidbody.isKinematic = true; - } - - private void FixedUpdate() - { - if (IsSpawned) - { - if (m_NetworkTransform.CanCommitToTransform != m_IsAuthority) - { - m_IsAuthority = m_NetworkTransform.CanCommitToTransform; - UpdateRigidbodyKinematicMode(); - } - } - } - - // Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server. - private void UpdateRigidbodyKinematicMode() - { - if (m_IsAuthority == false) - { - m_OriginalKinematic = m_Rigidbody.isKinematic; - m_Rigidbody.isKinematic = true; - - m_OriginalInterpolation = m_Rigidbody.interpolation; - // Set interpolation to none, the NetworkTransform component interpolates the position of the object. - m_Rigidbody.interpolation = RigidbodyInterpolation2D.None; - } - else - { - // Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost - m_Rigidbody.isKinematic = m_OriginalKinematic; - m_Rigidbody.interpolation = m_OriginalInterpolation; - } - } - - /// - public override void OnNetworkSpawn() - { - m_IsAuthority = m_NetworkTransform.CanCommitToTransform; - m_OriginalKinematic = m_Rigidbody.isKinematic; - m_OriginalInterpolation = m_Rigidbody.interpolation; - UpdateRigidbodyKinematicMode(); - } - - /// - public override void OnNetworkDespawn() + protected virtual void Awake() { - UpdateRigidbodyKinematicMode(); + Initialize(RigidbodyTypes.Rigidbody2D); } } } diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 9e51c75fdc..d65fc8d509 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1340,16 +1340,14 @@ internal NetworkTransformState LocalAuthoritativeNetworkState // DANGO-EXP TODO: ADD Rigidbody2D #if COM_UNITY_MODULES_PHYSICS private bool m_UseRigidbodyForMotion; - private Rigidbody m_RigidbodyInternal; - private NetworkRigidbody m_NetworkRigidbody; + private NetworkRigidbodyBase m_NetworkRigidbodyInternal; - internal void RegisterRigidbody(NetworkRigidbody networkRigidbody, Rigidbody rigidbody) + internal void RegisterRigidbody(NetworkRigidbodyBase networkRigidbody) { if (networkRigidbody != null) { - m_NetworkRigidbody = networkRigidbody; - m_UseRigidbodyForMotion = m_NetworkRigidbody.UseRigidBodyForMotion; - m_RigidbodyInternal = rigidbody; + m_NetworkRigidbodyInternal = networkRigidbody; + m_UseRigidbodyForMotion = m_NetworkRigidbodyInternal.UseRigidBodyForMotion; } } #endif @@ -1686,12 +1684,17 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var isRotationDirty = isTeleportingAndNotSynchronizing ? networkState.HasRotAngleChange : false; var isScaleDirty = isTeleportingAndNotSynchronizing ? networkState.HasScaleChange : false; +#if COM_UNITY_MODULES_PHYSICS + var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : InLocalSpace ? transformToUse.localPosition : transformToUse.position; + var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; +#else var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; - var rotAngles = InLocalSpace ? transformToUse.localEulerAngles : transformToUse.eulerAngles; + var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; +#endif + var rotAngles = rotation.eulerAngles; var scale = transformToUse.localScale; networkState.IsSynchronizing = isSynchronization; - // Check for parenting when synchronizing and/or teleporting if (isSynchronization || networkState.IsTeleportingNextFrame) { @@ -2111,8 +2114,8 @@ private void ApplyAuthoritativeState() // at the end of this method and assure that when not interpolating the non-authoritative side // cannot make adjustments to any portions the transform not being synchronized. #if COM_UNITY_MODULES_PHYSICS - var adjustedPosition = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : m_CurrentPosition; - var adjustedRotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : m_CurrentRotation; + var adjustedPosition = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : m_CurrentPosition; + var adjustedRotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : m_CurrentRotation; #else var adjustedPosition = m_CurrentPosition; var adjustedRotation = m_CurrentRotation; @@ -2245,8 +2248,7 @@ private void ApplyAuthoritativeState() #if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { - m_RigidbodyInternal.MovePosition(m_CurrentPosition); - transform.position = m_RigidbodyInternal.position; + m_NetworkRigidbodyInternal.MovePosition(m_CurrentPosition); } else #endif @@ -2274,8 +2276,7 @@ private void ApplyAuthoritativeState() #if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { - m_RigidbodyInternal.MoveRotation(m_CurrentRotation); - transform.rotation = m_RigidbodyInternal.rotation; + m_NetworkRigidbodyInternal.MoveRotation(m_CurrentRotation); } else #endif @@ -2396,7 +2397,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) #if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { - m_RigidbodyInternal.position = transform.position; + m_NetworkRigidbodyInternal.SetPosition(transform.position); } #endif @@ -2497,8 +2498,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) #if COM_UNITY_MODULES_PHYSICS if (m_UseRigidbodyForMotion) { - m_RigidbodyInternal.rotation = transform.rotation; - m_RigidbodyInternal.MoveRotation(transform.rotation); + m_NetworkRigidbodyInternal.SetRotation(transform.rotation); } #endif @@ -2876,8 +2876,8 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS - var position = m_UseRigidbodyForMotion ? m_RigidbodyInternal.position : GetSpaceRelativePosition(); - var rotation = m_UseRigidbodyForMotion ? m_RigidbodyInternal.rotation : GetSpaceRelativeRotation(); + var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); + var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); #else var position = GetSpaceRelativePosition(); var rotation = GetSpaceRelativeRotation(); @@ -2913,15 +2913,15 @@ private void InternalInitialization(bool isOwnershipChange = false) #if COM_UNITY_MODULES_PHYSICS // Depending upon order of operations, we invoke this in order to assure that proper settings are applied. - if (m_NetworkRigidbody) + if (m_NetworkRigidbodyInternal) { - m_NetworkRigidbody.UpdateOwnershipAuthority(); + m_NetworkRigidbodyInternal.UpdateOwnershipAuthority(); } if (m_UseRigidbodyForMotion) { - m_RigidbodyInternal.position = currentPosition; - m_RigidbodyInternal.rotation = currentRotation; + m_NetworkRigidbodyInternal.SetPosition(currentPosition); + m_NetworkRigidbodyInternal.SetRotation(currentRotation); } #endif @@ -3005,8 +3005,15 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj // Only if we are not authority if (!CanCommitToTransform) { - m_TargetPosition = m_CurrentPosition = GetSpaceRelativePosition(); - m_CurrentRotation = GetSpaceRelativeRotation(); +#if COM_UNITY_MODULES_PHYSICS + var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); + var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); +#else + var position = GetSpaceRelativePosition(); + var rotation = GetSpaceRelativeRotation(); +#endif + m_TargetPosition = m_CurrentPosition = position; + m_CurrentRotation = rotation; m_TargetRotation = m_CurrentRotation.eulerAngles; m_TargetScale = m_CurrentScale = GetScale(); @@ -3058,9 +3065,15 @@ public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? s NetworkLog.LogError(errorMessage); return; } - - Vector3 pos = posIn == null ? GetSpaceRelativePosition() : posIn.Value; - Quaternion rot = rotIn == null ? GetSpaceRelativeRotation() : rotIn.Value; +#if COM_UNITY_MODULES_PHYSICS + var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); + var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); +#else + var position = GetSpaceRelativePosition(); + var rotation = GetSpaceRelativeRotation(); +#endif + Vector3 pos = posIn == null ? position : posIn.Value; + Quaternion rot = rotIn == null ? rotation : rotIn.Value; Vector3 scale = scaleIn == null ? transform.localScale : scaleIn.Value; if (!CanCommitToTransform) @@ -3089,23 +3102,25 @@ public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? s ///
private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport) { - if (InLocalSpace) +#if COM_UNITY_MODULES_PHYSICS + if (m_UseRigidbodyForMotion) { - transform.localPosition = pos; - transform.localRotation = rot; + m_NetworkRigidbodyInternal.SetPosition(pos); + m_NetworkRigidbodyInternal.SetRotation(rot); } else +#endif { - transform.SetPositionAndRotation(pos, rot); - } - -#if COM_UNITY_MODULES_PHYSICS - if (m_UseRigidbodyForMotion && shouldTeleport) - { - m_RigidbodyInternal.position = transform.position; - m_RigidbodyInternal.rotation = transform.rotation; + if (InLocalSpace) + { + transform.localPosition = pos; + transform.localRotation = rot; + } + else + { + transform.SetPositionAndRotation(pos, rot); + } } -#endif transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; @@ -3129,7 +3144,6 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s // in between a fractional tick period and the current explicit set state did not find any deltas that we preserve any // previous dirty state. m_LocalAuthoritativeNetworkState.IsDirty = m_LocalAuthoritativeNetworkState.ExplicitSet; - } /// @@ -3272,10 +3286,7 @@ internal void OnFixedUpdate() return; } - if (m_RigidbodyInternal.IsSleeping()) - { - m_RigidbodyInternal.WakeUp(); - } + m_NetworkRigidbodyInternal.WakeIfSleeping(); // Update interpolation UpdateInterpolation(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 5e83ef2154..c34aebfa70 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -189,6 +189,9 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) SpawnManager.DeferredDespawnUpdate(ServerTime); } + // Update any NetworkObject's registered to notify of scene migration changes. + NetworkObject.UpdateNetworkObjectSceneChanges(); + // This should be invoked just prior to the MessageManager processes its outbound queue. SceneManager.CheckForAndSendNetworkObjectSceneChanged(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 5d4656e162..1c51865d98 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1143,12 +1143,6 @@ internal int GetSceneOriginHandle() return SceneOriginHandle != 0 ? SceneOriginHandle : gameObject.scene.handle; } - private void Awake() - { - SetCachedParent(transform.parent); - SceneOrigin = gameObject.scene; - } - /// /// Makes the previously hidden "netcode visible" to the targeted client. /// @@ -1937,6 +1931,13 @@ private void OnTransformParentChanged() if (NetworkManager == null || !NetworkManager.IsListening) { + // DANGO-TODO: Review as to whether we want to provide a better way to handle changing parenting of objects when the + // object is not spawned. Really, we shouldn't care about these types of changes. + if (NetworkManager.DistributedAuthorityMode && m_CachedParent != null && transform.parent == null) + { + m_CachedParent = null; + return; + } transform.parent = m_CachedParent; Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before reparenting")); return; @@ -2212,6 +2213,11 @@ internal void InvokeBehaviourNetworkSpawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); + if (SceneMigrationSynchronization && NetworkManager.NetworkConfig.EnableSceneManagement) + { + AddNetworkObjectToSceneChangedUpdates(this); + } + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) @@ -2240,6 +2246,11 @@ internal void InvokeBehaviourNetworkDespawn() { ChildNetworkBehaviours[i].InternalOnNetworkDespawn(); } + + if (SceneMigrationSynchronization && NetworkManager.NetworkConfig.EnableSceneManagement) + { + RemoveNetworkObjectFromSceneChangedUpdates(this); + } } private List m_ChildNetworkBehaviours; @@ -3013,6 +3024,37 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } + internal static Dictionary NetworkObjectsToSynchronizeSceneChanges = new Dictionary(); + + internal static void AddNetworkObjectToSceneChangedUpdates(NetworkObject networkObject) + { + if (!NetworkObjectsToSynchronizeSceneChanges.ContainsKey(networkObject.NetworkObjectId)) + { + NetworkObjectsToSynchronizeSceneChanges.Add(networkObject.NetworkObjectId, networkObject); + } + + networkObject.UpdateForSceneChanges(); + } + + internal static void RemoveNetworkObjectFromSceneChangedUpdates(NetworkObject networkObject) + { + NetworkObjectsToSynchronizeSceneChanges.Remove(networkObject.NetworkObjectId); + } + + internal static void UpdateNetworkObjectSceneChanges() + { + foreach(var entry in NetworkObjectsToSynchronizeSceneChanges) + { + entry.Value.UpdateForSceneChanges(); + } + } + + private void Awake() + { + SetCachedParent(transform.parent); + SceneOrigin = gameObject.scene; + } + /// /// Update /// Detects if a NetworkObject's scene has changed for both server and client instances @@ -3024,13 +3066,13 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) /// to add this same functionality to in-scene placed NetworkObjects until we have a way to generate /// per-NetworkObject-instance unique GlobalObjectIdHash values for in-scene placed NetworkObjects. /// - private void Update() + internal void UpdateForSceneChanges() { // Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle - if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager == null || NetworkManager.ShutdownInProgress - || IsSceneObject != false || gameObject.scene.handle == SceneOriginHandle) + if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager == null || NetworkManager.ShutdownInProgress || + !NetworkManager.NetworkConfig.EnableSceneManagement || IsSceneObject != false || gameObject.scene.handle == SceneOriginHandle) { return; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs index da8a0ed245..269312ea71 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs @@ -57,7 +57,9 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } } m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams); - if (!behaviour.IsServer) + + // In distributed authority mode, we don't need to send to the server identifier + if (!behaviour.IsServer && !m_NetworkManager.DistributedAuthorityMode) { m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); } From 400cdc05fc8c6bbe253554b25ff98693abc07d24 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 30 Mar 2024 16:48:23 -0500 Subject: [PATCH 028/236] test fix Fixing issue with some rigid body tests. Reverting change to NotMeTarget as it impacts UniversalRpc tests that I need to review with Kitty to determine the best way to modify. --- .../Components/NetworkRigidBodyBase.cs | 6 ++++ .../Components/NetworkTransform.cs | 6 +--- .../Messaging/RpcTargets/NotMeRpcTarget.cs | 4 ++- .../NetworkTransformOwnershipTests.cs | 35 ++++++++++++++++--- .../Runtime/Physics/NetworkRigidbodyTest.cs | 8 +++-- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs index c8bf7196c3..d156314b2a 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs @@ -372,6 +372,12 @@ public void SetIsKinematic(bool isKinematic) m_Rigidbody.isKinematic = isKinematic; } + // If we are not spawned, then exit early + if (!IsSpawned) + { + return; + } + if (UseRigidBodyForMotion) { // Only if the NetworkTransform is set to interpolate do we need to check for extrapolation diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index d65fc8d509..9f92a1de66 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -2113,13 +2113,9 @@ private void ApplyAuthoritativeState() // The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated // at the end of this method and assure that when not interpolating the non-authoritative side // cannot make adjustments to any portions the transform not being synchronized. -#if COM_UNITY_MODULES_PHYSICS - var adjustedPosition = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : m_CurrentPosition; - var adjustedRotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : m_CurrentRotation; -#else var adjustedPosition = m_CurrentPosition; var adjustedRotation = m_CurrentRotation; -#endif + var adjustedRotAngles = adjustedRotation.eulerAngles; var adjustedScale = m_CurrentScale; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs index 269312ea71..07a7edbc36 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs @@ -59,7 +59,9 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams); // In distributed authority mode, we don't need to send to the server identifier - if (!behaviour.IsServer && !m_NetworkManager.DistributedAuthorityMode) + // DANGO-TODO: Adding this causes UniversalRpc tests to fail. + // Get with Kitty to figure out the best way to update the UniversalRpc Tests for this update + if (!behaviour.IsServer)// && !m_NetworkManager.DistributedAuthorityMode) { m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 24d139a73e..39d9790f06 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -236,7 +236,9 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn yield return s_DefaultWaitForTick; - Assert.True(Approximately(nonOwnerInstance.transform.position, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{nonOwnerInstance.transform.position}"); + var testPosition = m_MotionModel == MotionModels.UseRigidbody ? nonOwnerInstance.GetComponent().position : nonOwnerInstance.transform.position; + + Assert.True(Approximately(testPosition, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{nonOwnerInstance.transform.position}"); // Change ownership and wait for the non-owner to reflect the change VerifyObjectIsSpawnedOnClient.ResetObjectTable(); @@ -284,6 +286,31 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn yield return new WaitForFixedUpdate(); yield return new WaitForFixedUpdate(); } + + Vector3 GetNonOwnerPosition() + { + if (m_MotionModel == MotionModels.UseRigidbody) + { + return nonOwnerInstance.GetComponent().position; + } + else + { + return nonOwnerInstance.transform.position; + } + } + + Quaternion GetNonOwnerRotation() + { + if (m_MotionModel == MotionModels.UseRigidbody) + { + return nonOwnerInstance.GetComponent().rotation; + } + else + { + return nonOwnerInstance.transform.rotation; + } + } + if (m_MotionModel == MotionModels.UseRigidbody) { m_UseAdjustedVariance = true; @@ -296,8 +323,8 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn ownerInstance.transform.position = valueSetByOwner; ownerInstance.transform.rotation = rotation; } - - yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation)); + + yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + @@ -306,7 +333,7 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn // The last check is to verify non-owners cannot change transform values after ownership has changed nonOwnerInstance.transform.position = Vector3.zero; yield return s_DefaultWaitForTick; - Assert.True(Approximately(nonOwnerInstance.transform.position, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{nonOwnerInstance.transform.position}"); + Assert.True(Approximately(GetNonOwnerPosition(), valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{GetNonOwnerPosition()}"); } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index c7b2fa5013..0a68e0d069 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -58,19 +58,21 @@ public IEnumerator TestRigidbodyKinematicEnableDisable() if (m_UseRigidBodyForMotion) { + var interpolateCompareNonAuthoritative = m_NetworkTransformInterpolate ? RigidbodyInterpolation.Interpolate : m_RigidbodyInterpolation; + // Server authoritative NT should yield non-kinematic mode for the server-side player instance Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. Assert.AreEqual(m_RigidbodyInterpolation, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); + $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {m_RigidbodyInterpolation}!"); // Server authoritative NT should yield kinematic mode for the client-side player instance Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used - Assert.AreEqual(RigidbodyInterpolation.Interpolate, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); + Assert.AreEqual(interpolateCompareNonAuthoritative, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + + $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {interpolateCompareNonAuthoritative}!"); } else { From 5877aad78cddefa94e919c9f145f63e297354e1b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 30 Mar 2024 17:33:43 -0500 Subject: [PATCH 029/236] test and style Fixing missing whitespace. Missed a few spots in the NetworkTransformOwnership tests. --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- .../NetworkTransform/NetworkTransformOwnershipTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 1c51865d98..7aa3a72026 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3043,7 +3043,7 @@ internal static void RemoveNetworkObjectFromSceneChangedUpdates(NetworkObject ne internal static void UpdateNetworkObjectSceneChanges() { - foreach(var entry in NetworkObjectsToSynchronizeSceneChanges) + foreach (var entry in NetworkObjectsToSynchronizeSceneChanges) { entry.Value.UpdateForSceneChanges(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 39d9790f06..9290eec5e6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -225,7 +225,7 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn }; ownerInstance.transform.rotation = rotation; var transformToTest = nonOwnerInstance.transform; - yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation)); + yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + @@ -267,7 +267,7 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn Assert.True(nonOwnerInstance.GetComponent().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!"); transformToTest = nonOwnerInstance.transform; - yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation)); + yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + @@ -323,7 +323,7 @@ Quaternion GetNonOwnerRotation() ownerInstance.transform.position = valueSetByOwner; ownerInstance.transform.rotation = rotation; } - + yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + From beebcd63f2f3171f2ec31148ea9616be9e351a98 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 30 Mar 2024 17:40:45 -0500 Subject: [PATCH 030/236] test fix Some more modifications to NetworkTransformOwnershipTests (waiting for FixedUpdate) --- .../NetworkTransformOwnershipTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 9290eec5e6..815ca6a5cd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -235,10 +235,12 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn nonOwnerInstance.transform.position = Vector3.zero; yield return s_DefaultWaitForTick; + if (m_MotionModel == MotionModels.UseRigidbody) + { + yield return new WaitForFixedUpdate(); + } - var testPosition = m_MotionModel == MotionModels.UseRigidbody ? nonOwnerInstance.GetComponent().position : nonOwnerInstance.transform.position; - - Assert.True(Approximately(testPosition, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{nonOwnerInstance.transform.position}"); + Assert.True(Approximately(GetNonOwnerPosition(), valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{GetNonOwnerPosition()}"); // Change ownership and wait for the non-owner to reflect the change VerifyObjectIsSpawnedOnClient.ResetObjectTable(); @@ -279,11 +281,10 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn ownerInstance.transform.localScale = valueSetByOwner; rotation.eulerAngles = valueSetByOwner; + yield return s_DefaultWaitForTick; // Allow scale to update first when using rigid body motion if (m_MotionModel == MotionModels.UseRigidbody) { - yield return s_DefaultWaitForTick; - yield return new WaitForFixedUpdate(); yield return new WaitForFixedUpdate(); } From 38fe8c2df38cf0927e672ddbc3a4530efc14b570 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 30 Mar 2024 20:30:28 -0500 Subject: [PATCH 031/236] test fix Adding wait for FixedUpdate in ServerAuthoritativeTest when using rigid body for motion. --- .../NetworkTransformOwnershipTests.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 815ca6a5cd..7e41c2eec3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -40,6 +40,7 @@ protected override void OnServerAndClientsCreated() clientNetworkTransform.UseHalfFloatPrecision = false; var rigidBody = m_ClientNetworkTransformPrefab.AddComponent(); rigidBody.useGravity = false; + rigidBody.interpolation = RigidbodyInterpolation.None; // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results @@ -51,6 +52,7 @@ protected override void OnServerAndClientsCreated() var networkTransform = m_NetworkTransformPrefab.AddComponent(); rigidBody = m_NetworkTransformPrefab.AddComponent(); rigidBody.useGravity = false; + rigidBody.interpolation = RigidbodyInterpolation.None; // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results @@ -281,12 +283,7 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn ownerInstance.transform.localScale = valueSetByOwner; rotation.eulerAngles = valueSetByOwner; - yield return s_DefaultWaitForTick; - // Allow scale to update first when using rigid body motion - if (m_MotionModel == MotionModels.UseRigidbody) - { - yield return new WaitForFixedUpdate(); - } + Vector3 GetNonOwnerPosition() { @@ -324,6 +321,12 @@ Quaternion GetNonOwnerRotation() ownerInstance.transform.position = valueSetByOwner; ownerInstance.transform.rotation = rotation; } + + // Allow scale to update first when using rigid body motion + if (m_MotionModel == MotionModels.UseRigidbody) + { + yield return new WaitForFixedUpdate(); + } yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + @@ -334,6 +337,11 @@ Quaternion GetNonOwnerRotation() // The last check is to verify non-owners cannot change transform values after ownership has changed nonOwnerInstance.transform.position = Vector3.zero; yield return s_DefaultWaitForTick; + // Allow scale to update first when using rigid body motion + if (m_MotionModel == MotionModels.UseRigidbody) + { + yield return new WaitForFixedUpdate(); + } Assert.True(Approximately(GetNonOwnerPosition(), valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{GetNonOwnerPosition()}"); } @@ -366,6 +374,12 @@ public IEnumerator ServerAuthoritativeTest() eulerAngles = valueSetByOwner }; ownerInstance.transform.rotation = rotation; + + // Allow scale to update first when using rigid body motion + if (m_MotionModel == MotionModels.UseRigidbody) + { + yield return new WaitForFixedUpdate(); + } var transformToTest = nonOwnerInstance.transform; yield return WaitForConditionOrTimeOut(() => transformToTest.position == valueSetByOwner && transformToTest.localScale == valueSetByOwner && transformToTest.rotation == rotation); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + From 65d974258ae476518db8f7e347de8b4f6e56c89f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 30 Mar 2024 20:51:43 -0500 Subject: [PATCH 032/236] style --- .../Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 7e41c2eec3..9482e92680 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -321,7 +321,7 @@ Quaternion GetNonOwnerRotation() ownerInstance.transform.position = valueSetByOwner; ownerInstance.transform.rotation = rotation; } - + // Allow scale to update first when using rigid body motion if (m_MotionModel == MotionModels.UseRigidbody) { From ba9f37ac1ccb43b8227ec218cce78ae7bd394897 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 31 Mar 2024 07:40:55 -0500 Subject: [PATCH 033/236] test --- .../NetworkTransform/NetworkTransformOwnershipTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 9482e92680..f42fbdba08 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -198,6 +198,10 @@ protected override float GetDeltaVarianceThreshold() [UnityTest] public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwnership) { + if (m_DistributedAuthority) + { + yield break; + } // Get the current ownership layout var networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; var networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; From 77ae80a1d6ec87563300ae1b914cf74e663cf379 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 31 Mar 2024 09:23:13 -0500 Subject: [PATCH 034/236] test still sorting this issue out --- .../NetworkTransformOwnershipTests.cs | 5 + .../Assets/AddressableAssetsData/link.xml | 23 ++ .../AddressableAssetsData/link.xml.meta | 7 + .../ProjectSettings/ProjectSettings.asset | 340 +++++++++++------- 4 files changed, 239 insertions(+), 136 deletions(-) create mode 100644 testproject/Assets/AddressableAssetsData/link.xml create mode 100644 testproject/Assets/AddressableAssetsData/link.xml.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index f42fbdba08..6676b13cd7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -394,6 +394,11 @@ public IEnumerator ServerAuthoritativeTest() // The last check is to verify clients cannot change transform values nonOwnerInstance.transform.position = Vector3.zero; yield return s_DefaultWaitForTick; + // Allow scale to update first when using rigid body motion + if (m_MotionModel == MotionModels.UseRigidbody) + { + yield return new WaitForFixedUpdate(); + } Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}"); } diff --git a/testproject/Assets/AddressableAssetsData/link.xml b/testproject/Assets/AddressableAssetsData/link.xml new file mode 100644 index 0000000000..080ccc63a3 --- /dev/null +++ b/testproject/Assets/AddressableAssetsData/link.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testproject/Assets/AddressableAssetsData/link.xml.meta b/testproject/Assets/AddressableAssetsData/link.xml.meta new file mode 100644 index 0000000000..ca82a0e4af --- /dev/null +++ b/testproject/Assets/AddressableAssetsData/link.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ca82ef8b6a0be8c41a08dc675bc4ba83 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 9fdad33958..32d7914435 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -3,7 +3,7 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 22 + serializedVersion: 26 productGUID: bba99b16607b94720b7d04f7f1a82989 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 @@ -17,7 +17,7 @@ PlayerSettings: defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} - m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashScreen: 0 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 @@ -48,14 +48,16 @@ PlayerSettings: defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 + unsupportedMSAAFallback: 0 + m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 mipStripping: 0 numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} m_StackTraceTypes: 010000000100000000000000000000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 - iosAllowHTTPDownload: 1 allowedAutorotateToPortrait: 1 allowedAutorotateToPortraitUpsideDown: 1 allowedAutorotateToLandscapeRight: 1 @@ -68,6 +70,13 @@ PlayerSettings: androidRenderOutsideSafeArea: 1 androidUseSwappy: 1 androidBlitType: 0 + androidResizableWindow: 0 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 @@ -79,6 +88,7 @@ PlayerSettings: hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 + dedicatedServerOptimizations: 0 bakeCollisionMeshes: 0 forceSingleInstance: 0 useFlipModelSwapchain: 1 @@ -113,20 +123,22 @@ PlayerSettings: switchNVNShaderPoolsGranularity: 33554432 switchNVNDefaultPoolsGranularity: 16777216 switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 + switchNVNGraphicsFirmwareMemory: 32 + switchMaxWorkerMultiple: 8 stadiaPresentMode: 0 stadiaTargetFramerate: 0 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 vulkanEnableLateAcquireNextImage: 0 - m_SupportedAspectRatios: - 4:3: 1 - 5:4: 1 - 16:10: 1 - 16:9: 1 - Others: 1 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 bundleVersion: 0.1 preloadedAssets: [] metroInputSource: 0 @@ -138,22 +150,26 @@ PlayerSettings: enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 useHDRDisplay: 0 - D3DHDRBitDepth: 0 + hdrBitDepth: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: Standalone: com.UnityTechnologies.testproject buildNumber: Standalone: 0 + VisionOS: 0 iPhone: 0 tvOS: 0 overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 19 + AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: nimt-trampolines=1024 @@ -166,12 +182,15 @@ PlayerSettings: APKExpansionFiles: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 1 + strictShaderVariantMatching: 0 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 - iOSTargetOSVersionString: 11.0 + iOSTargetOSVersionString: 12.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 11.0 + tvOSTargetOSVersionString: 12.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 @@ -196,7 +215,7 @@ PlayerSettings: rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 - iOSLaunchScreenCustomXibPath: + iOSLaunchScreenCustomXibPath: iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: @@ -204,22 +223,25 @@ PlayerSettings: rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 - iOSLaunchScreeniPadCustomXibPath: - iOSLaunchScreenCustomStoryboardPath: - iOSLaunchScreeniPadCustomStoryboardPath: + iOSLaunchScreeniPadCustomXibPath: + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] + macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 iOSRenderExtraFrameOnPause: 0 iosCopyPluginsCodeInsteadOfSymlink: 0 - appleDeveloperTeamID: - iOSManualSigningProvisioningProfileID: - tvOSManualSigningProvisioningProfileID: + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: iOSManualSigningProvisioningProfileType: 0 tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 appleEnableAutomaticSigning: 0 iOSRequireARKit: 0 iOSAutomaticallyDetectAndAddCapabilities: 1 @@ -234,12 +256,15 @@ PlayerSettings: useCustomLauncherGradleManifest: 0 useCustomBaseGradleTemplate: 0 useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 useCustomProguardFile: 0 AndroidTargetArchitectures: 1 + AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} - AndroidKeystoreName: - AndroidKeyaliasName: + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 0 AndroidIsGame: 1 @@ -252,7 +277,7 @@ PlayerSettings: height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 - AndroidMinifyWithR8: 0 + chromeosInputEmulation: 1 AndroidMinifyRelease: 0 AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 @@ -275,6 +300,7 @@ PlayerSettings: - m_BuildTarget: WebGL m_StaticBatching: 0 m_DynamicBatching: 0 + m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: - m_BuildTarget: MacStandaloneSupport m_GraphicsJobs: 0 @@ -310,7 +336,7 @@ PlayerSettings: m_BuildTargetGraphicsAPIs: - m_BuildTarget: AndroidPlayer m_APIs: 150000000b000000 - m_Automatic: 0 + m_Automatic: 1 - m_BuildTarget: iOSSupport m_APIs: 10000000 m_Automatic: 1 @@ -326,6 +352,8 @@ PlayerSettings: m_Devices: - Oculus - OpenVR + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 openGLRequireES31: 0 openGLRequireES31AEP: 0 openGLRequireES32: 0 @@ -335,59 +363,66 @@ PlayerSettings: iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] + m_BuildTargetGroupHDRCubemapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] m_BuildTargetNormalMapEncoding: [] + m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 1 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 - cameraUsageDescription: - locationUsageDescription: - microphoneUsageDescription: - switchNMETAOverride: - switchNetLibKey: + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 10.13.0 + switchNMETAOverride: + switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 - switchUseGOLDLinker: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 - switchNSODependencies: - switchTitleNames_0: - switchTitleNames_1: - switchTitleNames_2: - switchTitleNames_3: - switchTitleNames_4: - switchTitleNames_5: - switchTitleNames_6: - switchTitleNames_7: - switchTitleNames_8: - switchTitleNames_9: - switchTitleNames_10: - switchTitleNames_11: - switchTitleNames_12: - switchTitleNames_13: - switchTitleNames_14: - switchTitleNames_15: - switchPublisherNames_0: - switchPublisherNames_1: - switchPublisherNames_2: - switchPublisherNames_3: - switchPublisherNames_4: - switchPublisherNames_5: - switchPublisherNames_6: - switchPublisherNames_7: - switchPublisherNames_8: - switchPublisherNames_9: - switchPublisherNames_10: - switchPublisherNames_11: - switchPublisherNames_12: - switchPublisherNames_13: - switchPublisherNames_14: - switchPublisherNames_15: + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: switchIcons_0: {fileID: 0} switchIcons_1: {fileID: 0} switchIcons_2: {fileID: 0} @@ -420,19 +455,18 @@ PlayerSettings: switchSmallIcons_13: {fileID: 0} switchSmallIcons_14: {fileID: 0} switchSmallIcons_15: {fileID: 0} - switchManualHTML: - switchAccessibleURLs: - switchLegalInformation: + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: switchMainThreadStackSize: 1048576 - switchPresenceGroupId: + switchPresenceGroupId: switchLogoHandling: 0 switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 - switchTouchScreenUsage: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 - switchApplicationErrorCodeCategory: + switchApplicationErrorCodeCategory: switchUserAccountSaveDataSize: 0 switchUserAccountSaveDataJournalSize: 0 switchApplicationAttribute: 0 @@ -452,14 +486,14 @@ PlayerSettings: switchRatingsInt_10: 0 switchRatingsInt_11: 0 switchRatingsInt_12: 0 - switchLocalCommunicationIds_0: - switchLocalCommunicationIds_1: - switchLocalCommunicationIds_2: - switchLocalCommunicationIds_3: - switchLocalCommunicationIds_4: - switchLocalCommunicationIds_5: - switchLocalCommunicationIds_6: - switchLocalCommunicationIds_7: + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: switchParentalControl: 0 switchAllowsScreenshot: 1 switchAllowsVideoCapturing: 1 @@ -471,6 +505,7 @@ PlayerSettings: switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 @@ -481,40 +516,42 @@ PlayerSettings: switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 - switchPlayerConnectionEnabled: 1 switchUseNewStyleFilepaths: 0 + switchUseLegacyFmodPriorities: 0 switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 ps4NPAgeRating: 12 - ps4NPTitleSecret: - ps4NPTrophyPackPath: + ps4NPTitleSecret: + ps4NPTrophyPackPath: ps4ParentalLevel: 11 ps4ContentID: ED1633-NPXX51362_00-0000000000000000 ps4Category: 0 ps4MasterVersion: 01.00 ps4AppVersion: 01.00 ps4AppType: 0 - ps4ParamSfxPath: + ps4ParamSfxPath: ps4VideoOutPixelFormat: 0 ps4VideoOutInitialWidth: 1920 ps4VideoOutBaseModeInitialWidth: 1920 ps4VideoOutReprojectionRate: 60 - ps4PronunciationXMLPath: - ps4PronunciationSIGPath: - ps4BackgroundImagePath: - ps4StartupImagePath: - ps4StartupImagesFolder: - ps4IconImagesFolder: - ps4SaveDataImagePath: - ps4SdkOverride: - ps4BGMPath: - ps4ShareFilePath: - ps4ShareOverlayImagePath: - ps4PrivacyGuardImagePath: - ps4ExtraSceSysFile: - ps4NPtitleDatPath: + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: ps4RemotePlayKeyAssignment: -1 - ps4RemotePlayKeyMappingDir: + ps4RemotePlayKeyMappingDir: ps4PlayTogetherPlayerCount: 0 ps4EnterButtonAssignment: 1 ps4ApplicationParam1: 0 @@ -542,9 +579,9 @@ PlayerSettings: ps4ScriptOptimizationLevel: 0 ps4Audio3dVirtualSpeakerCount: 14 ps4attribCpuUsage: 0 - ps4PatchPkgPath: - ps4PatchLatestPkgPath: - ps4PatchChangeinfoPath: + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: ps4PatchDayOne: 0 ps4attribUserManagement: 0 ps4attribMoveSupport: 0 @@ -555,22 +592,24 @@ PlayerSettings: ps4videoRecordingFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0 ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 ps4GPU800MHz: 1 ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] ps4attribVROutputEnabled: 0 - monoEnv: + monoEnv: splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} blurSplashScreenBackground: 1 - spritePackerPolicy: + spritePackerPolicy: webGLMemorySize: 16 webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 webGLDataCaching: 1 webGLDebugSymbols: 0 - webGLEmscriptenArgs: - webGLModulesDirectory: + webGLEmscriptenArgs: + webGLModulesDirectory: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 @@ -579,39 +618,58 @@ PlayerSettings: webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 scriptingDefineSymbols: - 1: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT additionalCompilerArguments: - 1: + Standalone: - -warnaserror platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} - managedStrippingLevel: {} + il2cppCodeGeneration: {} + managedStrippingLevel: + EmbeddedLinux: 1 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + QNX: 1 + Stadia: 1 + VisionOS: 1 + WebGL: 1 + Windows Store Apps: 1 + XboxOne: 1 + iPhone: 1 + tvOS: 1 incrementalIl2cppBuild: {} suppressCommonWarnings: 1 allowUnsafeCode: 0 useDeterministicCompilation: 1 - useReferenceAssemblies: 1 - enableRoslynAnalyzers: 1 - additionalIl2CppArgs: + additionalIl2CppArgs: scriptingRuntimeVersion: 1 gcIncremental: 1 - assemblyVersionValidation: 1 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: Template_3D - metroPackageVersion: - metroCertificatePath: - metroCertificatePassword: - metroCertificateSubject: - metroCertificateIssuer: + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: metroCertificateNotAfter: 0000000000000000 metroApplicationDescription: Template_3D wsaImages: {} - metroTileShortName: + metroTileShortName: metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 @@ -626,22 +684,23 @@ PlayerSettings: metroSplashScreenUseBackgroundColor: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} - metroFTAName: + metroFTAName: metroFTAFileTypes: [] - metroProtocolName: - XboxOneProductId: - XboxOneUpdateKey: - XboxOneSandboxId: - XboxOneContentId: - XboxOneTitleId: - XboxOneSCId: - XboxOneGameOsOverridePath: - XboxOnePackagingOverridePath: - XboxOneAppManifestOverridePath: + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: XboxOneVersion: 1.0.0.0 XboxOnePackageEncryption: 0 XboxOnePackageUpdateGranularity: 2 - XboxOneDescription: + XboxOneDescription: XboxOneLanguage: - enus XboxOneCapability: [] @@ -654,8 +713,8 @@ PlayerSettings: XboxOneAllowedProductIds: [] XboxOnePersistentLocalStorageSize: 0 XboxOneXTitleMemory: 8 - XboxOneOverrideIdentityName: - XboxOneOverrideIdentityPublisher: + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: vrEditorSettings: {} cloudServicesEnabled: Analytics: 0 @@ -667,23 +726,32 @@ PlayerSettings: UNet: 1 Unity Ads: 0 luminIcon: - m_Name: - m_ModelFolderPath: - m_PortalFolderPath: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: luminCert: - m_CertPath: + m_CertPath: m_SignPackage: 1 luminIsChannelApp: 0 luminVersion: m_VersionCode: 1 - m_VersionName: + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 1 + hmiLogStartupTiming: 0 + hmiCpuConfiguration: apiCompatibilityLevel: 6 activeInputHandler: 0 - cloudProjectId: + windowsGamepadBackendHint: 0 + cloudProjectId: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] projectName: relay-stg organizationId: relay-stg cloudEnabled: 0 legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 From 7904e3de900d7b22cda141d8c1d099d2976137a0 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 31 Mar 2024 15:03:44 -0500 Subject: [PATCH 035/236] fix & test Fixing some additional issues with Rigidbody testing and assuring we are comparing the correct position and rotation. With NetworkTransformOwnershipTests, it turned out that the movement of the non-kinematic body was giving the Rigidbody a linear velocity under certain conditions. Removing the warning about resetting dirty when not initialized during NetworkVariableTests (removing a couple thousand log messages). Since the service is now consuming the initial synchronize event, updated how in-scene objects are spawned initially. --- .../Messages/NetworkTransformMessage.cs | 2 +- .../Components/NetworkTransform.cs | 45 ++++- .../Runtime/Spawning/NetworkSpawnManager.cs | 13 +- .../NetworkTransformOwnershipTests.cs | 172 +++++++++++------- .../Tests/Runtime/NetworkVariableTests.cs | 39 ++++ .../Assets/AddressableAssetsData/link.xml | 23 --- .../AddressableAssetsData/link.xml.meta | 7 - .../NetworkSceneManagerEventNotifications.cs | 1 + .../ProjectSettings/ProjectSettings.asset | 10 +- 9 files changed, 196 insertions(+), 116 deletions(-) delete mode 100644 testproject/Assets/AddressableAssetsData/link.xml delete mode 100644 testproject/Assets/AddressableAssetsData/link.xml.meta diff --git a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs index be5157b1ab..28a423ee9a 100644 --- a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs @@ -199,7 +199,7 @@ public void Handle(ref NetworkContext context) Debug.LogError($"[{nameof(NetworkTransformMessage)}][Dropped] Reciever {nameof(NetworkTransform)} was not set!"); return; } - m_ReceiverNetworkTransform.TransformStateUpdate(ref State); + m_ReceiverNetworkTransform.TransformStateUpdate(ref State, context.SenderId); } } } diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 9f92a1de66..3d0d0c1182 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Text; using Unity.Mathematics; using Unity.Netcode.Transports.UTP; using UnityEngine; @@ -2071,12 +2072,19 @@ private void OnNetworkTick() // As long as we are still authority if (CanCommitToTransform) { + if (m_CachedNetworkManager.DistributedAuthorityMode && !IsOwner) + { + Debug.LogError($"Non-owner Client-{m_CachedNetworkManager.LocalClientId} is being updated by network tick still!!!!"); + } // Update any changes to the transform var transformSource = transform; OnUpdateAuthoritativeState(ref transformSource); - +#if COM_UNITY_MODULES_PHYSICS + m_CurrentPosition = m_TargetPosition = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); +#else m_CurrentPosition = GetSpaceRelativePosition(); m_TargetPosition = GetSpaceRelativePosition(); +#endif } else // If we are no longer authority, unsubscribe to the tick event if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) @@ -2104,6 +2112,8 @@ internal void UpdatePositionInterpolator(Vector3 position, double time, bool res } } + internal bool LogMotion; + /// /// Applies the authoritative state to the transform /// @@ -2245,6 +2255,11 @@ private void ApplyAuthoritativeState() if (m_UseRigidbodyForMotion) { m_NetworkRigidbodyInternal.MovePosition(m_CurrentPosition); + if (LogMotion) + { + Debug.Log($"[Client-{m_CachedNetworkManager.LocalClientId}][Interpolate: {networkState.UseInterpolation}][TransPos: {transform.position}][RBPos: {m_NetworkRigidbodyInternal.GetPosition()}][CurrentPos: {m_CurrentPosition}"); + } + } else #endif @@ -2676,7 +2691,7 @@ protected virtual void OnNetworkTransformStateUpdated(ref NetworkTransformState } - + internal bool LogStateUpdate; /// /// Only non-authoritative instances should invoke this method /// @@ -2698,6 +2713,24 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf // Get the time when this new state was sent newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time; + if (LogStateUpdate) + { + var builder = new StringBuilder(); + builder.AppendLine($"[Client-{m_CachedNetworkManager.LocalClientId}][State Update: {newState.GetNetworkTick()}][HasPos: {newState.HasPositionChange}][Has Rot: {newState.HasRotAngleChange}][Has Scale: {newState.HasScaleChange}]"); + if (newState.HasPositionChange) + { + builder.AppendLine($"Position = {newState.GetPosition()}"); + } + if (newState.HasRotAngleChange) + { + builder.AppendLine($"Rotation = {newState.GetRotation()}"); + } + if (newState.HasScaleChange) + { + builder.AppendLine($"Scale = {newState.GetScale()}"); + } + Debug.Log(builder); + } // Apply the new state ApplyUpdatedState(newState); @@ -3324,8 +3357,12 @@ public bool IsServerAuthoritative() /// Invoked by to update the transform state /// /// - internal void TransformStateUpdate(ref NetworkTransformState networkTransformState) + internal void TransformStateUpdate(ref NetworkTransformState networkTransformState, ulong senderId) { + if (CanCommitToTransform) + { + Debug.LogError($"Authority receiving transform update from Client-{senderId}!"); + } // Store the previous/old state m_OldState = m_LocalAuthoritativeNetworkState; @@ -3364,7 +3401,7 @@ private void UpdateTransformState() State = m_LocalAuthoritativeNetworkState, DistributedAuthorityMode = m_CachedNetworkManager.DistributedAuthorityMode, // Don't populate if we are the DAHost as we send directly to each client - TargetIds = m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.DAHost ? NetworkObject.Observers.ToArray() : null, + TargetIds = m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.DAHost ? NetworkObject.Observers.Where((c) => c != m_CachedNetworkManager.LocalClientId).ToArray() : null, }; diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index a804c4d231..286b73e90d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1321,17 +1321,8 @@ internal void ServerSpawnSceneObjectsOnStartSweep() { ownerId = NetworkManager.LocalClientId; } - // DANGO-EXP TODO: Once the service consumes the initial SceneEventType.Synchronize sent to it, - // we should remove this. - if (isConnectedCMBService) - { - networkObjects[i].IsSceneObject = true; - networkObjects[i].Spawn(true); - } - else - { - SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true); - } + + SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 6676b13cd7..486a9e3388 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -41,6 +41,7 @@ protected override void OnServerAndClientsCreated() var rigidBody = m_ClientNetworkTransformPrefab.AddComponent(); rigidBody.useGravity = false; rigidBody.interpolation = RigidbodyInterpolation.None; + rigidBody.maxLinearVelocity = 0; // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results @@ -53,6 +54,7 @@ protected override void OnServerAndClientsCreated() rigidBody = m_NetworkTransformPrefab.AddComponent(); rigidBody.useGravity = false; rigidBody.interpolation = RigidbodyInterpolation.None; + rigidBody.maxLinearVelocity = 0; // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results @@ -198,10 +200,6 @@ protected override float GetDeltaVarianceThreshold() [UnityTest] public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwnership) { - if (m_DistributedAuthority) - { - yield break; - } // Get the current ownership layout var networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; var networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; @@ -216,22 +214,83 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn var nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId); Assert.NotNull(ownerInstance); Assert.NotNull(nonOwnerInstance); + Assert.True(networkManagerOwner.LocalClientId != networkManagerNonOwner.LocalClientId); + Assert.True(nonOwnerInstance.OwnerClientId != networkManagerNonOwner.LocalClientId); + Assert.True(nonOwnerInstance.NetworkManager.LocalClientId == networkManagerNonOwner.LocalClientId); + + Vector3 GetNonOwnerPosition() + { + if (m_MotionModel == MotionModels.UseRigidbody) + { + return nonOwnerInstance.GetComponent().position; + } + else + { + return nonOwnerInstance.transform.position; + } + } + + Quaternion GetNonOwnerRotation() + { + if (m_MotionModel == MotionModels.UseRigidbody) + { + return nonOwnerInstance.GetComponent().rotation; + } + else + { + return nonOwnerInstance.transform.rotation; + } + } + + void LogNonOwnerRigidBody(int stage) + { + if (m_MotionModel == MotionModels.UseRigidbody && m_EnableVerboseDebug) + { + var rigidbody = nonOwnerInstance.GetComponent(); + Debug.Log($"[{stage}][Rigidbody-NonOwner][Owner:{nonOwnerInstance.OwnerClientId} [Client-{nonOwnerInstance.NetworkManager.LocalClientId}][Gravity: {rigidbody.useGravity}][Kinematic: {rigidbody.isKinematic}][RB-Pos: {rigidbody.position}][RB-Rotation: {rigidbody.rotation}]"); + } + } + + void LogOwnerRigidBody(int stage) + { + if (m_MotionModel == MotionModels.UseRigidbody && m_EnableVerboseDebug) + { + var rigidbody = ownerInstance.GetComponent(); + Debug.Log($"[{stage}][Rigidbody-Owner][Owner:{ownerInstance.OwnerClientId} [Client-{ownerInstance.NetworkManager.LocalClientId}][Gravity: {rigidbody.useGravity}][Kinematic: {rigidbody.isKinematic}][RB-Pos: {rigidbody.position}][RB-Rotation: {rigidbody.rotation}]"); + } + } // Make sure the owner is not kinematic and the non-owner(s) are kinematic Assert.True(nonOwnerInstance.GetComponent().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!"); Assert.False(ownerInstance.GetComponent().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!"); - + if (m_MotionModel == MotionModels.UseRigidbody) + { + nonOwnerInstance.GetComponent().LogStateUpdate = m_EnableVerboseDebug; + } // Owner changes transform values var valueSetByOwner = Vector3.one * 2; - ownerInstance.transform.position = valueSetByOwner; - ownerInstance.transform.localScale = valueSetByOwner; var rotation = new Quaternion { eulerAngles = valueSetByOwner }; - ownerInstance.transform.rotation = rotation; + if (m_MotionModel == MotionModels.UseRigidbody) + { + var ownerRigidbody = ownerInstance.GetComponent(); + ownerRigidbody.Move(valueSetByOwner, rotation); + ownerRigidbody.velocity = Vector3.zero; + yield return s_DefaultWaitForTick; + ownerInstance.transform.localScale = valueSetByOwner; + } + else + { + ownerInstance.transform.position = valueSetByOwner; + ownerInstance.transform.rotation = rotation; + ownerInstance.transform.localScale = valueSetByOwner; + } + var transformToTest = nonOwnerInstance.transform; - yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); + LogNonOwnerRigidBody(1); + yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + @@ -239,13 +298,13 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn // Verify non-owners cannot change transform values nonOwnerInstance.transform.position = Vector3.zero; - yield return s_DefaultWaitForTick; if (m_MotionModel == MotionModels.UseRigidbody) { yield return new WaitForFixedUpdate(); } + LogNonOwnerRigidBody(2); Assert.True(Approximately(GetNonOwnerPosition(), valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{GetNonOwnerPosition()}"); // Change ownership and wait for the non-owner to reflect the change @@ -258,9 +317,11 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn { m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent(), networkManagerNonOwner.LocalClientId, true); } + LogNonOwnerRigidBody(3); yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent().OwnerClientId == networkManagerNonOwner.LocalClientId); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!"); + LogNonOwnerRigidBody(4); // Re-assign the ownership references and wait for the non-owner instance to be notified of ownership change networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; @@ -274,50 +335,31 @@ public IEnumerator OwnerAuthoritativeTest([Values] StartingOwnership startingOwn Assert.False(ownerInstance.GetComponent().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!"); Assert.True(nonOwnerInstance.GetComponent().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!"); transformToTest = nonOwnerInstance.transform; + Assert.True(networkManagerOwner.LocalClientId != networkManagerNonOwner.LocalClientId); + Assert.True(nonOwnerInstance.OwnerClientId != networkManagerNonOwner.LocalClientId); + Assert.True(nonOwnerInstance.NetworkManager.LocalClientId == networkManagerNonOwner.LocalClientId); - yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); + yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + $"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}"); - + LogNonOwnerRigidBody(5); // Have the new owner change transform values and wait for those values to be applied on the non-owner side. valueSetByOwner = Vector3.one * 10; ownerInstance.transform.localScale = valueSetByOwner; rotation.eulerAngles = valueSetByOwner; - - - - Vector3 GetNonOwnerPosition() - { - if (m_MotionModel == MotionModels.UseRigidbody) - { - return nonOwnerInstance.GetComponent().position; - } - else - { - return nonOwnerInstance.transform.position; - } - } - - Quaternion GetNonOwnerRotation() - { - if (m_MotionModel == MotionModels.UseRigidbody) - { - return nonOwnerInstance.GetComponent().rotation; - } - else - { - return nonOwnerInstance.transform.rotation; - } - } - + LogOwnerRigidBody(1); if (m_MotionModel == MotionModels.UseRigidbody) { m_UseAdjustedVariance = true; - var rigidBody = ownerInstance.GetComponent(); - rigidBody.Move(valueSetByOwner, rotation); + var ownerRigidbody = ownerInstance.GetComponent(); + ownerRigidbody.Move(valueSetByOwner, rotation); + LogOwnerRigidBody(2); + ownerInstance.GetComponent().LogMotion = m_EnableVerboseDebug; + nonOwnerInstance.GetComponent().LogMotion = m_EnableVerboseDebug; + ownerRigidbody.velocity = Vector3.zero; } else { @@ -326,13 +368,14 @@ Quaternion GetNonOwnerRotation() ownerInstance.transform.rotation = rotation; } - // Allow scale to update first when using rigid body motion - if (m_MotionModel == MotionModels.UseRigidbody) + LogOwnerRigidBody(3); + LogNonOwnerRigidBody(6); + yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); + if (s_GlobalTimeoutHelper.TimedOut) { - yield return new WaitForFixedUpdate(); + LogOwnerRigidBody(4); + LogNonOwnerRigidBody(7); } - - yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation)); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + @@ -341,7 +384,6 @@ Quaternion GetNonOwnerRotation() // The last check is to verify non-owners cannot change transform values after ownership has changed nonOwnerInstance.transform.position = Vector3.zero; yield return s_DefaultWaitForTick; - // Allow scale to update first when using rigid body motion if (m_MotionModel == MotionModels.UseRigidbody) { yield return new WaitForFixedUpdate(); @@ -472,24 +514,24 @@ public override void OnNetworkDespawn() [DisallowMultipleComponent] public class TestClientNetworkTransform : NetworkTransform { - public override void OnNetworkSpawn() - { - base.OnNetworkSpawn(); - CanCommitToTransform = IsOwner; - } - - protected override void Update() - { - CanCommitToTransform = IsOwner; - base.Update(); - if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening)) - { - if (CanCommitToTransform) - { - TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time); - } - } - } + //public override void OnNetworkSpawn() + //{ + // base.OnNetworkSpawn(); + // CanCommitToTransform = IsOwner; + //} + + //protected override void Update() + //{ + // CanCommitToTransform = IsOwner; + // base.Update(); + // if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening)) + // { + // if (CanCommitToTransform) + // { + // TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time); + // } + // } + //} protected override bool OnIsServerAuthoritative() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index eed032e296..bc625636a8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -17,6 +17,7 @@ public class NetworkVariablePermissionTests : NetcodeIntegrationTest { public static IEnumerable TestDataSource() { + NetworkVariableBase.IgnoreInitializeWarning = true; foreach (HostOrServer hostOrServer in Enum.GetValues(typeof(HostOrServer))) { // DANGO-EXP TODO: Add support for distributed authority mode @@ -26,6 +27,8 @@ public static IEnumerable TestDataSource() } yield return new TestFixtureData(hostOrServer); } + + NetworkVariableBase.IgnoreInitializeWarning = false; } protected override int NumberOfClients => 3; @@ -479,6 +482,18 @@ protected override bool CanStartServerAndClients() return false; } + protected override void OnOneTimeSetup() + { + NetworkVariableBase.IgnoreInitializeWarning = true; + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + NetworkVariableBase.IgnoreInitializeWarning = false; + base.OnOneTimeTearDown(); + } + /// /// This is an adjustment to how the server and clients are started in order /// to avoid timing issues when running in a stand alone test runner build. @@ -5359,6 +5374,18 @@ public bool CompareValuesC(ComponentC other) private GameObject m_TestObjectPrefab; private ulong m_TestObjectId = 0; + protected override void OnOneTimeSetup() + { + NetworkVariableBase.IgnoreInitializeWarning = true; + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + NetworkVariableBase.IgnoreInitializeWarning = false; + base.OnOneTimeTearDown(); + } + protected override void OnServerAndClientsCreated() { m_TestObjectPrefab = CreateNetworkObjectPrefab($"[{nameof(NetworkVariableInheritanceTests)}.{nameof(m_TestObjectPrefab)}]"); @@ -5454,6 +5481,18 @@ public class NetworkVariableModifyOnNetworkDespawn : NetcodeIntegrationTest private GameObject m_TestPrefab; + protected override void OnOneTimeSetup() + { + NetworkVariableBase.IgnoreInitializeWarning = true; + base.OnOneTimeSetup(); + } + + protected override void OnOneTimeTearDown() + { + NetworkVariableBase.IgnoreInitializeWarning = false; + base.OnOneTimeTearDown(); + } + protected override void OnServerAndClientsCreated() { m_TestPrefab = CreateNetworkObjectPrefab("NetVarDespawn"); diff --git a/testproject/Assets/AddressableAssetsData/link.xml b/testproject/Assets/AddressableAssetsData/link.xml deleted file mode 100644 index 080ccc63a3..0000000000 --- a/testproject/Assets/AddressableAssetsData/link.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testproject/Assets/AddressableAssetsData/link.xml.meta b/testproject/Assets/AddressableAssetsData/link.xml.meta deleted file mode 100644 index ca82a0e4af..0000000000 --- a/testproject/Assets/AddressableAssetsData/link.xml.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: ca82ef8b6a0be8c41a08dc675bc4ba83 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs index cae4bed259..34a2078499 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventNotifications.cs @@ -281,6 +281,7 @@ public IEnumerator SceneLoadingAndNotifications([Values] LoadSceneMode loadScene { additionalInfo += $"{sceneName},"; } + Debug.Break(); } AssertOnTimeout($"{nameof(m_ScenesLoaded)} still contains some of the scenes that were expected to be unloaded!\n {additionalInfo}"); } diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 32d7914435..8cc7aa6e3c 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -54,7 +54,7 @@ PlayerSettings: mipStripping: 0 numberOfMipsStripped: 0 numberOfMipsStrippedPerMipmapLimitGroup: {} - m_StackTraceTypes: 010000000100000000000000000000000100000001000000 + m_StackTraceTypes: 010000000100000001000000000000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 @@ -161,7 +161,7 @@ PlayerSettings: androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: - Standalone: com.UnityTechnologies.testproject + Standalone: com.Unity-Technologies.testproject buildNumber: Standalone: 0 VisionOS: 0 @@ -660,8 +660,8 @@ PlayerSettings: apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 - metroPackageName: Template_3D - metroPackageVersion: + metroPackageName: Template3D + metroPackageVersion: 1.0.0.0 metroCertificatePath: metroCertificatePassword: metroCertificateSubject: @@ -669,7 +669,7 @@ PlayerSettings: metroCertificateNotAfter: 0000000000000000 metroApplicationDescription: Template_3D wsaImages: {} - metroTileShortName: + metroTileShortName: testproject metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 From 2aa38ce046649629b1ab90d6d861915ed46b697c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 31 Mar 2024 16:35:54 -0500 Subject: [PATCH 036/236] update Missed update --- .../Runtime/NetworkVariable/NetworkVariableBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 9f4b90b543..e142fe9f8e 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -125,12 +125,17 @@ public virtual void SetDirty(bool isDirty) } } + internal static bool IgnoreInitializeWarning; + protected void MarkNetworkBehaviourDirty() { if (m_NetworkBehaviour == null) { - Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " + - "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); + if (!IgnoreInitializeWarning) + { + Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " + + "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); + } return; } if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress) From a0387117b160e213c8e57ce862c983e950371482 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 1 Apr 2024 11:04:39 -0500 Subject: [PATCH 037/236] update Adjusting the NotMeRpcTarget for distributed authority mode. --- .../Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs | 9 ++++----- testproject/ProjectSettings/ProjectSettings.asset | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs index 07a7edbc36..5ea687101c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs @@ -49,7 +49,8 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, { continue; } - if (clientId == NetworkManager.ServerClientId) + // In distributed authority mode, we send to target id 0 (which would be a DAHost) via the group + if (clientId == NetworkManager.ServerClientId && !m_NetworkManager.DistributedAuthorityMode) { continue; } @@ -58,10 +59,8 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams); - // In distributed authority mode, we don't need to send to the server identifier - // DANGO-TODO: Adding this causes UniversalRpc tests to fail. - // Get with Kitty to figure out the best way to update the UniversalRpc Tests for this update - if (!behaviour.IsServer)// && !m_NetworkManager.DistributedAuthorityMode) + // In distributed authority mode, we don't use ServerRpc + if (!behaviour.IsServer && !m_NetworkManager.DistributedAuthorityMode) { m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); } diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 8cc7aa6e3c..43f16b8278 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -17,7 +17,7 @@ PlayerSettings: defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} - m_ShowUnitySplashScreen: 0 + m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 From bcf94a74ecb15ddaed2eead28a849d690a892b9b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 1 Apr 2024 13:33:41 -0500 Subject: [PATCH 038/236] fix NetworkVariables of in-scene placed NetworkObjects not updating was due to the observers not being populated during the scene load event. --- .../Runtime/SceneManagement/SceneEventData.cs | 3 ++- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index e27f265ee9..f0293e4f98 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -665,6 +665,7 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) // Write our count place holder (must not be packed!) writer.WriteValueSafe((ushort)0); + var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects) { @@ -673,7 +674,7 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId)) { // Serialize the NetworkObject - var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId); + var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority); sceneObject.Serialize(writer); numberOfObjects++; } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 286b73e90d..482b6038e2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1089,7 +1089,7 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject var message = new CreateObjectMessage { - ObjectInfo = networkObject.GetMessageSceneObject(clientId), + ObjectInfo = networkObject.GetMessageSceneObject(clientId, NetworkManager.DistributedAuthorityMode), IncludesSerializedObject = true, UpdateObservers = NetworkManager.DistributedAuthorityMode, ObserverIds = NetworkManager.DistributedAuthorityMode ? networkObject.Observers.ToArray() : null, From 81a88fc82b44868a81ae61c73b91858758540356 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 1 Apr 2024 14:28:54 -0500 Subject: [PATCH 039/236] update When a session owner leaves, any remaining in-scene placed NetworkObjects belonging to the session owner changes ownership over to the newly promoted session owner. (temporary solution for this type of ownership change) --- .../Runtime/Core/NetworkManager.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index c34aebfa70..93c69a54a1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -132,8 +132,24 @@ public bool DAHost internal void SetSessionOwner(ulong sessionOwner) { + var previousSessionOwner = CurrentSessionOwner; CurrentSessionOwner = sessionOwner; LocalClient.IsSessionOwner = LocalClientId == sessionOwner; + if (LocalClient.IsSessionOwner) + { + foreach (var networkObjectEntry in SpawnManager.SpawnedObjects) + { + var networkObject = networkObjectEntry.Value; + if (!networkObject.IsSceneObject.Value) + { + continue; + } + if (networkObject.OwnerClientId != LocalClientId) + { + SpawnManager.ChangeOwnership(networkObject, LocalClientId, true); + } + } + } } // TODO: Make this internal after testing From bbdcf59694f21fb9dce8802ed7ec909f0e63ae44 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 1 Apr 2024 17:07:56 -0500 Subject: [PATCH 040/236] update & fix Fixing issue with null in-scene placed NetworkObject checking when session owner is assigned. Updating scene management (client side) to send the SynchronizeComplete message to all connected clients (so they will redistribute NetworkObjects evenly). --- .../Runtime/Core/NetworkManager.cs | 2 +- .../SceneManagement/NetworkSceneManager.cs | 35 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 93c69a54a1..64637c8027 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -140,7 +140,7 @@ internal void SetSessionOwner(ulong sessionOwner) foreach (var networkObjectEntry in SpawnManager.SpawnedObjects) { var networkObject = networkObjectEntry.Value; - if (!networkObject.IsSceneObject.Value) + if (networkObject.IsSceneObject == null || !networkObject.IsSceneObject.Value) { continue; } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index b305fc789a..ce03301129 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2336,15 +2336,22 @@ private void HandleClientSceneEvent(uint sceneEventId) sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete; if (NetworkManager.DistributedAuthorityMode) { - sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; - sceneEventData.SenderClientId = NetworkManager.LocalClientId; - var message = new SceneEventMessage + foreach (var clientId in NetworkManager.ConnectedClientsIds) { - EventData = sceneEventData, - }; - var target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); - NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); + if (clientId == NetworkManager.LocalClientId) + { + continue; + } + sceneEventData.TargetClientId = clientId; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + var message = new SceneEventMessage + { + EventData = sceneEventData, + }; + var target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); + NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); + } } else { @@ -2493,13 +2500,17 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) } case SceneEventType.SynchronizeComplete: { - - // At this point the client is considered fully "connected" if ((NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) || !NetworkManager.DistributedAuthorityMode) { if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) { + // DANGO-EXP TODO: Remove this once service is sending the synchronization message to all clients + if (NetworkManager.ConnectedClients.ContainsKey(clientId) && NetworkManager.ConnectionManager.ConnectedClientIds.Contains(clientId) && NetworkManager.ConnectedClientsList.Contains(NetworkManager.ConnectedClients[clientId])) + { + EndSceneEvent(sceneEventId); + return; + } NetworkManager.ConnectionManager.AddClient(clientId); } @@ -2517,6 +2528,10 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) } else { + // DANGO-EXP TODO: Remove this once service distributes objects + // Non-session owners receive this notification from newly connected clients and upon receiving + // the event they will redistribute their NetworkObjects + NetworkManager.SpawnManager.DistributeNetworkObjects(clientId); EndSceneEvent(sceneEventId); return; } From 0f34139e784dcbd57dab993fc468cedd84ff9adb Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 1 Apr 2024 17:54:34 -0500 Subject: [PATCH 041/236] test fix DAHost integration test expects the single synchronization complete message. --- .../SceneManagement/NetworkSceneManager.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index ce03301129..b2ca1e2182 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2336,13 +2336,28 @@ private void HandleClientSceneEvent(uint sceneEventId) sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete; if (NetworkManager.DistributedAuthorityMode) { - foreach (var clientId in NetworkManager.ConnectedClientsIds) + if (NetworkManager.CMBServiceConnection) { - if (clientId == NetworkManager.LocalClientId) + foreach (var clientId in NetworkManager.ConnectedClientsIds) { - continue; + if (clientId == NetworkManager.LocalClientId) + { + continue; + } + sceneEventData.TargetClientId = clientId; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + var message = new SceneEventMessage + { + EventData = sceneEventData, + }; + var target = NetworkManager.DAHost ? NetworkManager.CurrentSessionOwner : NetworkManager.ServerClientId; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, target); + NetworkManager.NetworkMetrics.TrackSceneEventSent(target, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), size); } - sceneEventData.TargetClientId = clientId; + } + else + { + sceneEventData.TargetClientId = NetworkManager.CurrentSessionOwner; sceneEventData.SenderClientId = NetworkManager.LocalClientId; var message = new SceneEventMessage { From e7ac3c896eea828bced614363711b80dcad4d570 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 3 Apr 2024 08:27:09 -0500 Subject: [PATCH 042/236] chore: update to for Unity6 support (#2863) * update make the unity 6 editor the target * update updating the target editor to use for all tests * update Update changes from PascalCasing to camelCasing. Updating the manifest.json. Updating the ProjectVersion. * fixes and update fixing an issue with one of the scene integration tests. Updating the addressable asset settings for Unity 6 compatibility. other minor updates. * update NGO version number. Added a large chunk of the changelog entries. --- .yamato/project.metafile | 15 +++-- com.unity.netcode.gameobjects/CHANGELOG.md | 62 ++++++++++++++++++- .../RigidbodyContactEventManager.cs | 18 +++--- .../SceneManagement/NetworkSceneManager.cs | 2 +- .../SceneManagement/SceneEventProgress.cs | 2 +- .../NetworkTransformOwnershipTests.cs | 4 +- com.unity.netcode.gameobjects/package.json | 4 +- .../AddressableAssetSettings.asset | 47 ++++++++------ .../SceneObjectsNotDestroyedOnShutdownTest.cs | 16 ++--- testproject/Packages/manifest.json | 27 ++++---- .../ProjectSettings/ProjectVersion.txt | 4 +- 11 files changed, 134 insertions(+), 67 deletions(-) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 6a840717ff..191842fd5e 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -1,5 +1,5 @@ -validation_editor: 2022.3 -mobile_validation_editor: 2022.3 +validation_editor: 6000.0 +mobile_validation_editor: 6000.0 # Platforms that will be tested. The first entry in this array will also # be used for validation @@ -42,8 +42,7 @@ projects: - name: com.unity.netcode.gameobjects path: com.unity.netcode.gameobjects test_editors: - - 2022.3 - - 2023.2 + - 6000.0 - trunk - name: minimalproject path: minimalproject @@ -54,22 +53,22 @@ projects: - name: com.unity.netcode.gameobjects path: com.unity.netcode.gameobjects test_editors: - - 2022.3 + - 6000.0 - name: testproject-tools-integration path: testproject-tools-integration validate: false publish: false has_tests: true test_editors: - - 2022.3 + - 6000.0 - trunk # Package dependencies dependencies: - name: com.unity.transport - version: 2.0.0-pre.2 + version: 2.2.1 test_editors: - - 2022.2 + - 6000.0 - trunk # Scripting backends used by Standalone Playmode Tests diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c45db7c11e..57402364ca 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,9 +6,61 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] +## [2.0.0-exp.2] - 2024-04-02 ### Added +- Added updates to all internal messages to account for a distributed authority network session connection. (#2863) +- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) + - For a customized `NetworkRigidbodyBase` class: + - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes. + - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned. + - `NetworkRigidbodyBase.Initialize` is a protected method that, when invoked, will initialize the instance. This includes options to: + - Set whether using a `RigidbodyTypes.Rigidbody` or `RigidbodyTypes.Rigidbody2D`. + - Includes additional optional parameters to set the `NetworkTransform`, `Rigidbody`, and `Rigidbody2d` to use. + - Provides additional public methods: + - `NetworkRigidbodyBase.GetPosition` to return the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.GetRotation` to return the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.MovePosition` to move to the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.MoveRotation` to move to the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.SetPosition` to set the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.SetRotation` to set the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). + - `NetworkRigidbodyBase.ApplyCurrentTransform` to set the position and rotation of the `Rigidbody` or `Rigidbody2d` based on the associated `GameObject` transform (depending upon its initialized setting). + - `NetworkRigidbodyBase.WakeIfSleeping` to wake up the rigid body if sleeping. + - `NetworkRigidbodyBase.SleepRigidbody` to put the rigid body to sleep. + - `NetworkRigidbodyBase.IsKinematic` to determine if the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) is currently kinematic. + - `NetworkRigidbodyBase.SetIsKinematic` to set the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) current kinematic state. + - `NetworkRigidbodyBase.ResetInterpolation` to reset the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) back to its original interpolation value when initialized. + - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned `NetworkTransform` when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863) +- Added `RigidbodyContactEventManager` that provides a more optimized way to process collision enter and collision stay events as opposed to the `Monobehaviour` approach. (#2863) + - Can be used in client-server and distributed authority modes, but is particularly useful in distributed authority. +- Added rigid body motion updates to `NetworkTransform` which allows users to set interolation on rigid bodies. (#2863) + - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or `NetworkRigidbody` or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation. +- Added distributed authority mode support to `NetworkAnimator`. (#2863) +- Added session mode selection to `NetworkManager` inspector view. (#2863) +- Added distributed authority permissions feature. (#2863) +- Added distributed authority mode specific `NetworkObject` permissions flags (Distributable, Transferable, and RequestRequired). (#2863) +- Added distributed authority mode specific `NetworkObject.SetOwnershipStatus` method that applies one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) +- Added distributed authority mode specific `NetworkObject.RemoveOwnershipStatus` method that removes one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) +- Added distributed authority mode specific `NetworkObject.HasOwnershipStatus` method that will return (true or false) whether one or more ownership flags is set. (#2863) +- Added distributed authority mode specific `NetworkObject.SetOwnershipLock` method that locks ownership of a `NetworkObject` to prevent ownership from changing until the current owner releases the lock. (#2863) +- Added distributed authority mode specific `NetworkObject.RequestOwnership` method that sends an ownership request to the current owner of a spawned `NetworkObject` instance. (#2863) +- Added distributed authority mode specific `NetworkObject.OnOwnershipRequested` callback handler that is invoked on the owner/authoritative side when a non-owner requests ownership. Depending upon the boolean returned value depends upon whether the request is approved or denied. (#2863) +- Added distributed authority mode specific `NetworkObject.OnOwnershipRequestResponse` callback handler that is invoked when a non-owner's request has been processed. This callback includes a `NetworkObjet.OwnershipRequestResponseStatus` response parameter that describes whether the request was approved or the reason why it was not approved. (#2863) +- Added distributed authority mode specific `NetworkObject.DeferDespawn` method that defers the despawning of `NetworkObject` instances on non-authoritative clients based on the tick offset parameter. (#2863) +- Added distributed authority mode specific `NetworkObject.OnDeferredDespawnComplete` callback handler that can be used to further control when deferring the despawning of a `NetworkObject` on non-authoritative instances. (#2863) +- Added `NetworkClient.SessionModeType` as one way to determine the current session mode of the network session a client is connected to. (#2863) +- Added distributed authority mode specific `NetworkClient.IsSessionOwner` property to determine if the current local client is the current session owner of a distributed authority session. (#2863) +- Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is authomatically the owner of the spawned object). (#2863) + - This is useful to better visually synchronize owner authoritative motion models and newly spawned `NetworkObject` instances (i.e. projectiles for example). +- Added distributed authority mode specific client side player spawning capabilities. Clients will automatically spawn their associated player object locally. (#2863) +- Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863) +- Added distributed authority mode specific `NetworkManager.OnFetchLocalPlayerPrefabToSpawn` callback that, when assigned, will allow the local client to provide the player prefab to be spawned for the local client. (#2863) + - This is only invoked if the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property is set to true. +- Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a `NetworkBehaviour` script much like that of `IsServer` or `IsClient`). (#2863) +- Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a `NetworkBehaviour` script). (#2863) +- Added support for distributed authority mode scene management where the currently assigned session owner can start scene events (i.e. scene loading and scene unloading). (#2863) ### Fixed @@ -17,7 +69,13 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) ### Changed - +- Changed client side awareness of other clients is now the same as a server or host. (#2863) +- Changed `NetworkManager.ConnectedClients` can now be accessed by both server and clients. (#2863) +- Changed `NetworkManager.ConnectedClientsList` can now be accessed by both server and clients. (#2863) +- Changed `NetworkTransform` defaults to owner authoritative when connected to a distributed authority session. (#2863) +- Changed `NetworkVariable` defaults to owner write and everyone read permissions when connected to a distributed authority session (even if declared with server read or write permissions). (#2863) +- Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by `NetworkManager`. (#2863) +- Changed `NetworkManager` inspector view layout where properties are now organized by category. (#2863) - Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807) diff --git a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs index 688aa68e97..9471a94fb5 100644 --- a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs @@ -170,28 +170,28 @@ public void Execute(int index) int count = 0; int collisionStaycount = 0; int positionCount = 0; - for (int j = 0; j < PairedHeaders[index].PairCount; j++) + for (int j = 0; j < PairedHeaders[index].pairCount; j++) { ref readonly var pair = ref PairedHeaders[index].GetContactPair(j); - if (pair.IsCollisionExit) + if (pair.isCollisionExit) { continue; } - for (int k = 0; k < pair.ContactCount; k++) + for (int k = 0; k < pair.contactCount; k++) { ref readonly var contact = ref pair.GetContactPoint(k); - averagePoint += contact.Position; + averagePoint += contact.position; positionCount++; - if (!pair.IsCollisionStay) + if (!pair.isCollisionStay) { - averageNormal += contact.Normal; + averageNormal += contact.normal; count++; } else { - averageCollisionStay += contact.Normal; + averageCollisionStay += contact.normal; collisionStaycount++; } } @@ -214,8 +214,8 @@ public void Execute(int index) var result = new JobResultStruct() { - ThisInstanceID = PairedHeaders[index].BodyInstanceID, - OtherInstanceID = PairedHeaders[index].OtherBodyInstanceID, + ThisInstanceID = PairedHeaders[index].bodyInstanceID, + OtherInstanceID = PairedHeaders[index].otherBodyInstanceID, AverageNormal = averageNormal, HasCollisionStay = collisionStaycount != 0, AverageCollisionStayNormal = averageCollisionStay, diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index b2ca1e2182..1e6dacfd84 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1888,7 +1888,7 @@ private void OnSessionOwnerLoadedScene(uint sceneEventId, Scene scene) OnLoadComplete?.Invoke(NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress - if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsHost) + if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsClient) { SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.LocalClientId); } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index 734d9885ee..e9b52a218e 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -300,7 +300,7 @@ internal void TryFinishingSceneEventProgress() m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; } - if (m_TimeOutCoroutine != null) + if (m_TimeOutCoroutine != null && m_NetworkManager != null) { m_NetworkManager.StopCoroutine(m_TimeOutCoroutine); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 486a9e3388..d6d35f397c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -277,7 +277,7 @@ void LogOwnerRigidBody(int stage) { var ownerRigidbody = ownerInstance.GetComponent(); ownerRigidbody.Move(valueSetByOwner, rotation); - ownerRigidbody.velocity = Vector3.zero; + ownerRigidbody.linearVelocity = Vector3.zero; yield return s_DefaultWaitForTick; ownerInstance.transform.localScale = valueSetByOwner; } @@ -359,7 +359,7 @@ void LogOwnerRigidBody(int stage) LogOwnerRigidBody(2); ownerInstance.GetComponent().LogMotion = m_EnableVerboseDebug; nonOwnerInstance.GetComponent().LogMotion = m_EnableVerboseDebug; - ownerRigidbody.velocity = Vector3.zero; + ownerRigidbody.linearVelocity = Vector3.zero; } else { diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 3db90c2849..df8a300be1 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,8 +2,8 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0-exp.1", - "unity": "2022.3", + "version": "2.0.0-exp.2", + "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.transport": "2.2.1" diff --git a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 87c46389ef..6a1c39a006 100644 --- a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -13,24 +13,39 @@ MonoBehaviour: m_Name: AddressableAssetSettings m_EditorClassIdentifier: m_DefaultGroup: 93aa504d1b753cb41a8a779ae63f5795 - m_CachedHash: + m_currentHash: serializedVersion: 2 - Hash: 00000000000000000000000000000000 + Hash: c105cac3e36a87a6dabc4f3755bc1fd9 m_OptimizeCatalogSize: 0 m_BuildRemoteCatalog: 0 - m_BundleLocalCatalog: 0 m_CatalogRequestsTimeout: 0 m_DisableCatalogUpdateOnStart: 0 + m_InternalIdNamingMode: 0 + m_InternalBundleIdMode: 1 + m_AssetLoadMode: 0 + m_BundledAssetProviderType: + m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider + m_AssetBundleProviderType: + m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider m_IgnoreUnsupportedFilesInBuild: 0 m_UniqueBundleIds: 0 + m_EnableJsonCatalog: 0 m_NonRecursiveBuilding: 1 m_CCDEnabled: 0 m_maxConcurrentWebRequests: 500 + m_UseUWRForLocalBundles: 0 + m_BundleTimeout: 0 + m_BundleRetryCount: 0 + m_BundleRedirectLimit: -1 + m_SharedBundleSettings: 0 + m_SharedBundleSettingsCustomGroupIndex: 0 m_ContiguousBundles: 0 m_StripUnityVersionFromBundleBuild: 0 m_DisableVisibleSubAssetRepresentations: 0 - m_ShaderBundleNaming: 0 - m_ShaderBundleCustomNaming: + m_BuiltInBundleNaming: 0 + mBuiltInBundleCustomNaming: m_MonoScriptBundleNaming: 0 m_CheckForContentUpdateRestrictionsOption: 0 m_MonoScriptBundleCustomNaming: @@ -47,8 +62,6 @@ MonoBehaviour: - {fileID: 11400000, guid: 783a7ba7cfd87084388760e6c08547b4, type: 2} - {fileID: 11400000, guid: cb34f2b4f49b0a64aa04c7c5bcb3e41e, type: 2} m_BuildSettings: - m_CompileScriptsInVirtualMode: 0 - m_CleanupStreamingAssetsAfterBuilds: 1 m_LogResourceManagerExceptions: 1 m_BundleBuildPath: Temp/com.unity.addressables/AssetBundles m_ProfileSettings: @@ -64,24 +77,24 @@ MonoBehaviour: - m_Id: 80085c797452bb94ca0bf0a4b2ec258c m_Value: '{UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]' - m_Id: eaae5cc67c56cfe4299363a742a284b3 - m_Value: ServerData/[BuildTarget] + m_Value: 'ServerData/[BuildTarget]' - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 - m_Value: http://localhost/[BuildTarget] + m_Value: 'http://localhost/[BuildTarget]' m_ProfileEntryNames: - m_Id: a2553e7788fb43741926b3128a0aa641 m_Name: BuildTarget m_InlineUsage: 0 - m_Id: 8bc200e556c0a2f4daf1a4696958eaa6 - m_Name: LocalBuildPath + m_Name: Local.BuildPath m_InlineUsage: 0 - m_Id: 80085c797452bb94ca0bf0a4b2ec258c - m_Name: LocalLoadPath + m_Name: Local.LoadPath m_InlineUsage: 0 - m_Id: eaae5cc67c56cfe4299363a742a284b3 - m_Name: RemoteBuildPath + m_Name: Remote.BuildPath m_InlineUsage: 0 - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 - m_Name: RemoteLoadPath + m_Name: Remote.LoadPath m_InlineUsage: 0 m_ProfileVersion: 1 m_LabelTable: @@ -94,15 +107,9 @@ MonoBehaviour: m_CertificateHandlerType: m_AssemblyName: m_ClassName: - m_ActivePlayerDataBuilderIndex: 3 + m_ActivePlayerDataBuilderIndex: 2 m_DataBuilders: - {fileID: 11400000, guid: 9f220b782e4e67d4787c06a5ca9f25cc, type: 2} - - {fileID: 11400000, guid: 34e64d32387bb4a4db2e4170f8100ace, type: 2} - {fileID: 11400000, guid: ae43701986fc59a44b2618f3bacabc0a, type: 2} - {fileID: 11400000, guid: d6f3a7ab824a4b04a970de0ba5011a93, type: 2} m_ActiveProfileId: b7ba5fc73af2a49449023b732cdf652d - m_HostingServicesManager: - m_HostingServiceInfos: [] - m_Settings: {fileID: 11400000} - m_NextInstanceId: 0 - m_RegisteredServiceTypeRefs: [] diff --git a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs index 4c190c424c..c0d5e87b04 100644 --- a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs +++ b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs @@ -27,17 +27,19 @@ public IEnumerator SceneObjectsNotDestroyedOnShutdown() yield return WaitForConditionOrTimeOut(() => m_TestScene.IsValid() && m_TestScene.isLoaded); AssertOnTimeout($"Timed out waiting for scene {k_TestScene} to load!"); - #if UNITY_2023_1_OR_NEWER - var loadedInSceneObject = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName).FirstOrDefault(); + var loadedInSceneObject = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name.Contains(k_SceneObjectName)).FirstOrDefault(); #else var loadedInSceneObject = Object.FindObjectsOfType().Where((c) => c.name.Contains(k_SceneObjectName)).FirstOrDefault(); #endif Assert.IsNotNull(loadedInSceneObject, $"Failed to find {k_SceneObjectName} before starting client!"); + + AssertOnTimeout($"Timed out waiting to find {k_SceneObjectName} after scene load and before starting client!\""); + yield return CreateAndStartNewClient(); #if UNITY_2023_1_OR_NEWER - var loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName); + var loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name.Contains(k_SceneObjectName)); #else var loadedInSceneObjects = Object.FindObjectsOfType().Where((c) => c.name.Contains(k_SceneObjectName)); #endif @@ -45,7 +47,7 @@ public IEnumerator SceneObjectsNotDestroyedOnShutdown() m_ClientNetworkManagers[0].Shutdown(); yield return m_DefaultWaitForTick; #if UNITY_2023_1_OR_NEWER - loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName); + loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name.Contains(k_SceneObjectName)); #else loadedInSceneObjects = Object.FindObjectsOfType().Where((c) => c.name.Contains(k_SceneObjectName)); #endif @@ -62,7 +64,7 @@ public IEnumerator ChildSceneObjectsDoNotDestroyOnShutdown() AssertOnTimeout($"Timed out waiting for scene {k_TestScene} to load!"); #if UNITY_2023_1_OR_NEWER - var loadedInSceneObject = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName).FirstOrDefault(); + var loadedInSceneObject = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name.Contains(k_SceneObjectName)).FirstOrDefault(); #else var loadedInSceneObject = Object.FindObjectsOfType().Where((c) => c.name.Contains(k_SceneObjectName)).FirstOrDefault(); #endif @@ -76,7 +78,7 @@ public IEnumerator ChildSceneObjectsDoNotDestroyOnShutdown() AssertOnTimeout($"Client-{clientId} player never parented {k_SceneObjectName}!"); #if UNITY_2023_1_OR_NEWER - var loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName); + var loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name.Contains(k_SceneObjectName)); #else var loadedInSceneObjects = Object.FindObjectsOfType().Where((c) => c.name.Contains(k_SceneObjectName)); #endif @@ -88,7 +90,7 @@ public IEnumerator ChildSceneObjectsDoNotDestroyOnShutdown() yield return WaitForConditionOrTimeOut(() => PlayerNoLongerExistsWithChildren(clientId)); AssertOnTimeout($"Client-{clientId} player still exits with children after client shutdown!"); #if UNITY_2023_1_OR_NEWER - loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name == k_SceneObjectName); + loadedInSceneObjects = Object.FindObjectsByType(FindObjectsSortMode.InstanceID).Where((c) => c.name.Contains(k_SceneObjectName)); #else loadedInSceneObjects = Object.FindObjectsOfType().Where((c) => c.name.Contains(k_SceneObjectName)); #endif diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 427d544b3e..5e655da7ed 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,21 +1,22 @@ { "dependencies": { - "com.unity.addressables": "1.21.9", - "com.unity.ai.navigation": "1.1.1", - "com.unity.collab-proxy": "1.17.7", - "com.unity.ide.rider": "3.0.18", - "com.unity.ide.visualstudio": "2.0.17", - "com.unity.mathematics": "1.2.6", + "com.unity.addressables": "2.0.8", + "com.unity.ai.navigation": "2.0.0", + "com.unity.collab-proxy": "2.3.1", + "com.unity.ide.rider": "3.0.28", + "com.unity.ide.visualstudio": "2.0.22", + "com.unity.mathematics": "1.3.1", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.49.0-preview", - "com.unity.services.authentication": "2.4.0", - "com.unity.services.core": "1.8.1", + "com.unity.services.authentication": "3.3.0", + "com.unity.services.core": "1.12.4", "com.unity.services.relay": "1.0.5", - "com.unity.test-framework": "1.3.3", - "com.unity.test-framework.performance": "2.8.1-preview", - "com.unity.textmeshpro": "3.0.6", - "com.unity.timeline": "1.8.2", - "com.unity.ugui": "1.0.0", + "com.unity.test-framework": "1.4.3", + "com.unity.test-framework.performance": "3.0.3", + "com.unity.timeline": "1.8.6", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", + "com.unity.ugui": "2.0.0", + "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index 307ccd3516..af45f2f719 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.19f1 -m_EditorVersionWithRevision: 2022.3.19f1 (244b723c30a6) +m_EditorVersion: 6000.0.0b13 +m_EditorVersionWithRevision: 6000.0.0b13 (21aeb48b6ed2) From b0106c3cd54c5014cbd35077b4eb752bb7785df8 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 25 Apr 2024 15:13:40 -0500 Subject: [PATCH 043/236] chore updating develop-2.0.0 to match the develop branch --- .github/CODEOWNERS | 14 +++++++------- .github/workflows/autoupdate.yaml | 1 + .github/workflows/conventional-pr.yml | 1 + .yamato/_triggers.yml | 3 ++- .yamato/project-standards.yml | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c9443a2f7a..ac441af2c8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,10 +7,10 @@ Metrics/ @Unity-Technologies/multiplayer-tools /com.unity.netcode.gameobjects/Runtime/Transports/ @Unity-Technologies/multiplayer-workflows /com.unity.netcode.gameobjects/Tests/Editor/Transports/ @Unity-Technologies/multiplayer-workflows /com.unity.netcode.gameobjects/Tests/Runtime/Transports/ @Unity-Technologies/multiplayer-workflows -*.asmdef @chrispope @tylerunity -package.json @chrispope @tylerunity -AssemblyInfo.cs @chrispope @tylerunity -.editorconfig @chrispope @tylerunity -.gitignore @chrispope @tylerunity -.github/ @chrispope @tylerunity -.yamato/ @chrispope @tylerunity +*.asmdef @chrispope @miniwolf +package.json @chrispope @miniwolf +AssemblyInfo.cs @chrispope @miniwolf +.editorconfig @chrispope @miniwolf +.gitignore @chrispope @miniwolf +.github/ @chrispope @miniwolf +.yamato/ @chrispope @miniwolf diff --git a/.github/workflows/autoupdate.yaml b/.github/workflows/autoupdate.yaml index 30df1efe54..764b52b373 100644 --- a/.github/workflows/autoupdate.yaml +++ b/.github/workflows/autoupdate.yaml @@ -3,6 +3,7 @@ on: push: branches: - develop + - develop-2.0.0 jobs: autoupdate: name: auto-update diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 38889741a6..6090fe2ab2 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -5,6 +5,7 @@ on: pull_request: branches: - develop + - develop-2.0.0 # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index fa3be8d6f9..e225500f59 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -62,8 +62,9 @@ pull_request_trigger: only: - "master" - "develop" + - "develop-2.0.0" - "/release\/.*/" - + # Currently, we need to have a trigger to updated badges # Only package badges currently exist badges_test_trigger: diff --git a/.yamato/project-standards.yml b/.yamato/project-standards.yml index 7fc50c638e..a5147a9dce 100644 --- a/.yamato/project-standards.yml +++ b/.yamato/project-standards.yml @@ -4,7 +4,7 @@ standards_{{ projects.first.name }}: name: Standards Check {{ projects.first.name }} agent: type: Unity::VM - image: desktop/logging-testing-linux:v0.1.2-926285 + image: package-ci/ubuntu-20.04:v4 flavor: b1.large commands: - dotnet --version From b449ea89af642373a47611d72e96a57adacc3bb4 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 25 Apr 2024 17:43:52 -0500 Subject: [PATCH 044/236] fix: in-scene networkobject with networktransform synchronization when parented under gameobject (up-port of 2895) [MTT-8402] (#2898) * fix Fixing issue where parenting synchronization on the authority side was not properly handling the scenario where a NetworkObject is parented under a GameObject (with or without auto object parent sync enabled). * fix Fixing issue where parenting synchronization on the non-authority side was not properly handling the scenario where a NetworkObject is parented under a GameObject (with or without auto object parent sync enabled). * update migrate when to apply in local space above the parenting check. * test Adding an integration test to validate an in-scene placed NetworkObject's initial synchronization when parented under a GameObject. This also test both scenarios where the in-scene placed NetworkObject does and does not have a NetworkTransform. * fix removing white space after added enabled field during a cherry pick merge. * style removing unused using directive. * update updating some package versions * update updating the version of linux used * Update CODEOWNERS just catching up the CODEOWNERS v2.0 develop branch to changes in v1.0 branch * update adding changelog entry * Update CHANGELOG.md --- com.unity.netcode.gameobjects/CHANGELOG.md | 11 + .../Components/NetworkTransform.cs | 32 ++- .../Runtime/Spawning/NetworkSpawnManager.cs | 13 +- .../InSceneUnderGameObject.unity | 229 +++++++++++++++ .../InSceneUnderGameObject.unity.meta | 7 + .../InSceneUnderGameObjectWithNT.unity | 261 ++++++++++++++++++ .../InSceneUnderGameObjectWithNT.unity.meta | 7 + .../InScenePlacedNetworkObjectTests.cs | 183 +++++++++++- .../NetworkObjectTestComponent.cs | 20 +- testproject/Packages/manifest.json | 8 +- .../ProjectSettings/EditorBuildSettings.asset | 7 + 11 files changed, 742 insertions(+), 36 deletions(-) create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity.meta create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 57402364ca..fe295d0187 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [Unreleased] + +### Added + +### Fixed + +- Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2898) + +### Changed + + ## [2.0.0-exp.2] - 2024-04-02 ### Added diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 3d0d0c1182..254369a28c 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1696,6 +1696,15 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var scale = transformToUse.localScale; networkState.IsSynchronizing = isSynchronization; + // All of the checks below, up to the delta position checking portion, are to determine if the + // authority changed a property during runtime that requires a full synchronizing. + if (InLocalSpace != networkState.InLocalSpace) + { + networkState.InLocalSpace = InLocalSpace; + isDirty = true; + networkState.IsTeleportingNextFrame = true; + } + // Check for parenting when synchronizing and/or teleporting if (isSynchronization || networkState.IsTeleportingNextFrame) { @@ -1705,11 +1714,13 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // values are applied. var hasParentNetworkObject = false; + var parentNetworkObject = (NetworkObject)null; + // If the NetworkObject belonging to this NetworkTransform instance has a parent // (i.e. this handles nested NetworkTransforms under a parent at some layer above) if (NetworkObject.transform.parent != null) { - var parentNetworkObject = NetworkObject.transform.parent.GetComponent(); + parentNetworkObject = NetworkObject.transform.parent.GetComponent(); // In-scene placed NetworkObjects parented under a GameObject with no // NetworkObject preserve their lossyScale when synchronizing. @@ -1730,28 +1741,25 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // the NetworkTransform is using world or local space synchronization. // WorldPositionStays: (always use world space) // !WorldPositionStays: (always use local space) - if (isSynchronization) + // Exception: If it is an in-scene placed NetworkObject and it is parented under a GameObject + // then always use local space unless AutoObjectParentSync is disabled and the NetworkTransform + // is synchronizing in world space. + if (isSynchronization && networkState.IsParented) { - if (NetworkObject.WorldPositionStays()) + var parentedUnderGameObject = NetworkObject.transform.parent != null && !parentNetworkObject && NetworkObject.IsSceneObject.Value; + if (NetworkObject.WorldPositionStays() && (!parentedUnderGameObject || (parentedUnderGameObject && !NetworkObject.AutoObjectParentSync && !InLocalSpace))) { position = transformToUse.position; + networkState.InLocalSpace = false; } else { position = transformToUse.localPosition; + networkState.InLocalSpace = true; } } } - // All of the checks below, up to the delta position checking portion, are to determine if the - // authority changed a property during runtime that requires a full synchronizing. - if (InLocalSpace != networkState.InLocalSpace) - { - networkState.InLocalSpace = InLocalSpace; - isDirty = true; - networkState.IsTeleportingNextFrame = true; - } - if (Interpolate != networkState.UseInterpolation) { networkState.UseInterpolation = Interpolate; diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 482b6038e2..210c249cde 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -810,20 +810,27 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO networkObject.DontDestroyWithOwner = sceneObject.DontDestroyWithOwner; networkObject.Ownership = (NetworkObject.OwnershipStatus)sceneObject.OwnershipFlags; + + var nonNetworkObjectParent = false; // SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject) // This is a special case scenario where a late joining client has joined and loaded one or // more scenes that contain nested in-scene placed NetworkObject children yet the server's // synchronization information does not indicate the NetworkObject in question has a parent. // Under this scenario, we want to remove the parent before spawning and setting the transform values. - if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null) + if (sceneObject.IsSceneObject && networkObject.transform.parent != null) { + var parentNetworkObject = networkObject.transform.parent.GetComponent(); // if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not // include parenting, then we need to force the removal of that parent - if (networkObject.transform.parent.GetComponent() != null) + if (!sceneObject.HasParent && parentNetworkObject) { // remove the parent networkObject.ApplyNetworkParenting(true, true); } + else if (sceneObject.HasParent && !parentNetworkObject) + { + nonNetworkObjectParent = true; + } } // Set the transform unless we were spawned by a prefab handler @@ -833,7 +840,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO { // If world position stays is true or we have auto object parent synchronization disabled // then we want to apply the position and rotation values world space relative - if (worldPositionStays || !networkObject.AutoObjectParentSync) + if ((worldPositionStays && !nonNetworkObjectParent) || !networkObject.AutoObjectParentSync) { networkObject.transform.position = position; networkObject.transform.rotation = rotation; diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity new file mode 100644 index 0000000000..ba9f95cfd3 --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity @@ -0,0 +1,229 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.37311953, g: 0.38074014, b: 0.3587274, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &151892557 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 151892558} + m_Layer: 0 + m_Name: ParentNoNT + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &151892558 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 151892557} + serializedVersion: 2 + m_LocalRotation: {x: 0.6465042, y: 0.021078281, z: 0.723563, w: -0.24092445} + m_LocalPosition: {x: 2, y: 4, z: 1} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 386611893} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: -20, y: 80, z: 200} +--- !u!1 &386611892 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 386611893} + - component: {fileID: 386611894} + - component: {fileID: 386611895} + m_Layer: 0 + m_Name: ChildNoNT + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &386611893 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 386611892} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 151892558} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &386611894 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 386611892} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 4010782011 + InScenePlacedSourceGlobalObjectIdHash: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &386611895 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 386611892} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd877da52a3b48f4f867ac2c973c0265, type: 3} + m_Name: + m_EditorClassIdentifier: + ObjectWasDisabledUponSpawn: 0 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 151892558} diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity.meta new file mode 100644 index 0000000000..e8c308a091 --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 778ea62ad5daee5408d1ec1fce28673f +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity new file mode 100644 index 0000000000..7d57191d46 --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity @@ -0,0 +1,261 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &1010367930 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1010367931} + - component: {fileID: 1010367932} + - component: {fileID: 1010367933} + - component: {fileID: 1010367934} + m_Layer: 0 + m_Name: ChildNT + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1010367931 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1010367930} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1523284014} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1010367932 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1010367930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1318288622 + InScenePlacedSourceGlobalObjectIdHash: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &1010367933 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1010367930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} + m_Name: + m_EditorClassIdentifier: + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 0 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 0 + Interpolate: 1 + SlerpPosition: 0 +--- !u!114 &1010367934 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1010367930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd877da52a3b48f4f867ac2c973c0265, type: 3} + m_Name: + m_EditorClassIdentifier: + ObjectWasDisabledUponSpawn: 0 +--- !u!1 &1523284013 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1523284014} + m_Layer: 0 + m_Name: ParentNT + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1523284014 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1523284013} + serializedVersion: 2 + m_LocalRotation: {x: -0.06162833, y: 0.7044161, z: -0.40557978, w: 0.579228} + m_LocalPosition: {x: 5, y: 2, z: 3} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1010367931} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 30, y: 90, z: -40} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1523284014} diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity.meta new file mode 100644 index 0000000000..bc6449311e --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5a4f489df08d16c4d8c0167b099de2ca +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index cf82da52d2..22b4e53c52 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Unity.Netcode; +using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.SceneManagement; @@ -14,23 +15,38 @@ namespace TestProject.RuntimeTests { [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] - public class InScenePlacedNetworkObjectTests : NetcodeIntegrationTest + 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 bool m_CanStartServerAndClients; + private string m_SceneLoading = k_SceneToLoad; public InScenePlacedNetworkObjectTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } protected override IEnumerator OnSetup() { NetworkObjectTestComponent.Reset(); + NetworkObjectTestComponent.VerboseDebug = m_EnableVerboseDebug; m_CanStartServerAndClients = false; 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() + { + yield return CleanUpLoadedScene(); + } + protected override bool CanStartServerAndClients() { return m_CanStartServerAndClients; @@ -187,7 +203,7 @@ public IEnumerator ParentedInSceneObjectLateJoiningClient() private void OnSceneEvent(SceneEvent sceneEvent) { - if (sceneEvent.SceneEventType == SceneEventType.LoadComplete && sceneEvent.SceneName == k_SceneToLoad && sceneEvent.ClientId == m_ClientNetworkManagers[0].LocalClientId) + if (sceneEvent.SceneEventType == SceneEventType.LoadComplete && sceneEvent.SceneName == m_SceneLoading && sceneEvent.ClientId == m_ClientNetworkManagers[0].LocalClientId) { m_ClientLoadedScene = sceneEvent.Scene; } @@ -398,17 +414,168 @@ private void SceneManager_OnLoadEventCompleted(string sceneName, LoadSceneMode l m_SceneLoaded = SceneManager.GetSceneByName(sceneName); } + public enum ParentSyncSettings + { + ParentSync, + NoParentSync + } + + public enum TransformSyncSettings + { + TransformSync, + NoTransformSync + } + public enum TransformSpace + { + World, + Local + } /// - /// 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! + /// 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. /// - /// - protected override IEnumerator OnTearDown() + /// 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) { - yield return CleanUpLoadedScene(); + var useNetworkTransform = m_SceneLoading == k_InSceneUnderWithNT; + + m_SceneLoading = inSceneUnderToLoad; + // Because despawning a client will cause it to shutdown and clean everything in the + // scene hierarchy, we have to prevent one of the clients from spawning initially before + // we test synchronizing late joining clients. + // So, we prevent the automatic starting of the server and clients, remove the client we + // will be targeting to join late from the m_ClientNetworkManagers array, start the server + // and the remaining client, despawn the in-scene NetworkObject, and then start and synchronize + // the clientToTest. + var clientToTest = m_ClientNetworkManagers[1]; + var clients = m_ClientNetworkManagers.ToList(); + + // 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. + + clients.Remove(clientToTest); + m_ClientNetworkManagers = clients.ToArray(); + m_CanStartServerAndClients = true; + yield return StartServerAndClients(); + clients.Add(clientToTest); + m_ClientNetworkManagers = clients.ToArray(); + + NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + + m_ClientNetworkManagers[0].SceneManager.OnSceneEvent += OnSceneEvent; + m_ServerNetworkManager.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!"); + + m_ClientNetworkManagers[0].SceneManager.OnSceneEvent -= OnSceneEvent; + var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; + Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); + var firstClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.Where((c) => c.NetworkManager == m_ClientNetworkManagers[0]).FirstOrDefault(); + Assert.IsNotNull(firstClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)}!"); + Assert.IsTrue(firstClientInSceneObjectInstance.NetworkManager == m_ClientNetworkManagers[0]); + + // Parent the object + var clientSideServerPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][NetworkManager.ServerClientId]; + + 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 + NetcodeIntegrationTestHelpers.StartOneClient(clientToTest); + yield return WaitForConditionOrTimeOut(() => (clientToTest.IsConnectedClient && clientToTest.IsListening)); + AssertOnTimeout($"Timed out waiting for {clientToTest.name} to reconnect!"); + + yield return s_DefaultWaitForTick; + + // Update the newly joined client information + ClientNetworkManagerPostStartInit(); + + var lateJoinClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.Where((c) => c.NetworkManager == m_ClientNetworkManagers[1]).FirstOrDefault(); + 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; + } + } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs index c403e96bbd..f2ea87833d 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs @@ -12,6 +12,7 @@ namespace TestProject.RuntimeTests /// public class NetworkObjectTestComponent : NetworkBehaviour { + public static bool VerboseDebug; public static bool DisableOnDespawn; public static bool DisableOnSpawn; public static NetworkObject ServerNetworkObjectInstance; @@ -73,11 +74,7 @@ public override void OnNetworkDespawn() { OnInSceneObjectDespawned?.Invoke(NetworkObject); m_HasNotifiedSpawned = false; - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"{NetworkManager.name} de-spawned {gameObject.name}."); - } - + LogMessage($"{NetworkManager.name} de-spawned {gameObject.name}."); SpawnedInstances.Remove(this); if (DisableOnDespawn) { @@ -93,12 +90,17 @@ private void Update() // We do this so the ObjectNameIdentifier has a chance to label it properly if (IsSpawned && !m_HasNotifiedSpawned) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - Debug.Log($"{NetworkManager.name} spawned {gameObject.name} with scene origin handle {gameObject.scene.handle}."); - } + LogMessage($"{NetworkManager.name} spawned {gameObject.name} with scene origin handle {gameObject.scene.handle}."); m_HasNotifiedSpawned = true; } } + + private void LogMessage(string message) + { + if (VerboseDebug) + { + Debug.Log(message); + } + } } } diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 5e655da7ed..7f68fd1136 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -5,13 +5,13 @@ "com.unity.collab-proxy": "2.3.1", "com.unity.ide.rider": "3.0.28", "com.unity.ide.visualstudio": "2.0.22", - "com.unity.mathematics": "1.3.1", + "com.unity.mathematics": "1.3.2", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.49.0-preview", - "com.unity.services.authentication": "3.3.0", - "com.unity.services.core": "1.12.4", + "com.unity.services.authentication": "3.3.1", + "com.unity.services.core": "1.12.5", "com.unity.services.relay": "1.0.5", - "com.unity.test-framework": "1.4.3", + "com.unity.test-framework": "1.4.4", "com.unity.test-framework.performance": "3.0.3", "com.unity.timeline": "1.8.6", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index 553549cc12..6d435293c4 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -143,6 +143,13 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.unity guid: abe92ece17d830e41a66dff7edc9245d + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObject.unity + guid: 778ea62ad5daee5408d1ec1fce28673f + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity + guid: 5a4f489df08d16c4d8c0167b099de2ca m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} + m_UseUCBPForAssetBundles: 0 From db2315ce8e95463cfce92de445b1b8b28f1a3b64 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 26 Apr 2024 15:59:41 -0500 Subject: [PATCH 045/236] fix Minor NetworkTransform fix to prevent error logging of messages received by a previous owner. If NetworkTransform is not active then don't check for updates during the tick update. --- .../Components/NetworkTransform.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 254369a28c..2769b8db5f 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -2077,6 +2077,12 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra /// private void OnNetworkTick() { + // If not active, then ignore the update + if (!gameObject.activeInHierarchy) + { + return; + } + // As long as we are still authority if (CanCommitToTransform) { @@ -3369,7 +3375,9 @@ internal void TransformStateUpdate(ref NetworkTransformState networkTransformSta { if (CanCommitToTransform) { - Debug.LogError($"Authority receiving transform update from Client-{senderId}!"); + // TODO: Investigate where this state should be applied or just discarded. + // For now, discard the state if we assumed ownership. + return; } // Store the previous/old state m_OldState = m_LocalAuthoritativeNetworkState; From be8a02a86efbf8d0989b7ac9a252fe02c4a7d755 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 1 May 2024 21:11:49 -0500 Subject: [PATCH 046/236] feat: add pre and post spawn methods [MTT-8470] up-port v2.0.0 (#2912) * update adding pre and post spawn methods. clearing the m_ChildNetworkBehaviours on Awake. * update adding change log entries * update Needed to add a NetworkManager ref into the pre spawn method. Needed to account for locally spawning. * test Adding test that validates the pre spawn and post spawn methods are invoked and that order of operations for OnNetworkSpawn invocation is not an issue with OnNetworkPostSpawn invocation. * style updating comments * style updating comments a bit more * test Migrating OnDynamicNetworkPreAndPostSpawn into its own class to avoid interfering with the generic tests. * style removing property no longer used and remove LF/CR * update and test Added NetworkBehaviour.OnNetworkSessionSynchronized and NetworkBehaviour.OnInSceneObjectsSpawned methods. Added test to validate the above methods. Added assets for the test to validate the above methods. * update Merge fix * test Updates for distributed authority testing. * Update CHANGELOG.md Adding v2.0.0 PR * style removing white spaces --- com.unity.netcode.gameobjects/CHANGELOG.md | 37 ++- .../Runtime/Core/NetworkBehaviour.cs | 89 ++++++ .../Runtime/Core/NetworkObject.cs | 52 ++++ .../Messages/ConnectionApprovedMessage.cs | 6 + .../SceneManagement/NetworkSceneManager.cs | 17 ++ .../Runtime/SceneManagement/SceneEventData.cs | 25 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 21 ++ .../NetworkBehaviourPrePostSpawnTests.cs | 143 +++++++++ .../NetworkBehaviourPrePostSpawnTests.cs.meta | 11 + .../HybridScripts/SessionSynchronizedTest.cs | 62 ++++ .../SessionSynchronizedTest.cs.meta | 11 + .../SessionSynchronize.unity | 271 ++++++++++++++++++ .../SessionSynchronize.unity.meta | 7 + .../NetworkBehaviourSessionSynchronized.cs | 51 ++++ ...etworkBehaviourSessionSynchronized.cs.meta | 11 + .../ProjectSettings/EditorBuildSettings.asset | 3 + 16 files changed, 814 insertions(+), 3 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs.meta create mode 100644 testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs create mode 100644 testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs.meta create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity.meta create mode 100644 testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index fe295d0187..9cd30aaa16 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,8 +10,13 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2912) +- Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2912) +- Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2912) + ### Fixed +- Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2912) - Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2898) ### Changed @@ -63,7 +68,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added distributed authority mode specific `NetworkObject.OnDeferredDespawnComplete` callback handler that can be used to further control when deferring the despawning of a `NetworkObject` on non-authoritative instances. (#2863) - Added `NetworkClient.SessionModeType` as one way to determine the current session mode of the network session a client is connected to. (#2863) - Added distributed authority mode specific `NetworkClient.IsSessionOwner` property to determine if the current local client is the current session owner of a distributed authority session. (#2863) -- Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is authomatically the owner of the spawned object). (#2863) +- Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is automatically the owner of the spawned object). (#2863) - This is useful to better visually synchronize owner authoritative motion models and newly spawned `NetworkObject` instances (i.e. projectiles for example). - Added distributed authority mode specific client side player spawning capabilities. Clients will automatically spawn their associated player object locally. (#2863) - Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863) @@ -90,6 +95,36 @@ Additional documentation and release notes are available at [Multiplayer Documen - Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807) +## [1.9.1] - 2024-04-18 + +### Added +- Added `AnticipatedNetworkVariable`, which adds support for client anticipation of NetworkVariable values, allowing for more responsive game play (#2820) +- Added `AnticipatedNetworkTransform`, which adds support for client anticipation of `NetworkTransform`s (#2820) +- Added `NetworkVariableBase.ExceedsDirtinessThreshold` to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in NetworkVariable with the callback NetworkVariable.CheckExceedsDirtinessThreshold) (#2820) +- Added `NetworkVariableUpdateTraits`, which add additional throttling support: `MinSecondsBetweenUpdates` will prevent the `NetworkVariable` from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while `MaxSecondsBetweenUpdates` will force a dirty `NetworkVariable` to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2820) +- Added virtual method `NetworkVariableBase.OnInitialize()` which can be used by `NetworkVariable` subclasses to add initialization code (#2820) +- Added virtual method `NetworkVariableBase.Update()`, which is called once per frame to support behaviors such as interpolation between an anticipated value and an authoritative one. (#2820) +- Added `NetworkTime.TickWithPartial`, which represents the current tick as a double that includes the fractional/partial tick value. (#2820) +- `NetworkVariable` now includes built-in support for `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, and `Dictionary` (#2813) +- `NetworkVariable` now includes delta compression for collection values (`NativeList`, `NativeArray`, `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, `Dictionary`, and `FixedString` types) to save bandwidth by only sending the values that changed. (Note: For `NativeList`, `NativeArray`, and `List`, this algorithm works differently than that used in `NetworkList`. This algorithm will use less bandwidth for "set" and "add" operations, but `NetworkList` is more bandwidth-efficient if you are performing frequent "insert" operations.) (#2813) +- `UserNetworkVariableSerialization` now has optional callbacks for `WriteDelta` and `ReadDelta`. If both are provided, they will be used for all serialization operations on NetworkVariables of that type except for the first one for each client. If either is missing, the existing `Write` and `Read` will always be used. (#2813) +- Network variables wrapping `INetworkSerializable` types can perform delta serialization by setting `UserNetworkVariableSerialization.WriteDelta` and `UserNetworkVariableSerialization.ReadDelta` for those types. The built-in `INetworkSerializable` serializer will continue to be used for all other serialization operations, but if those callbacks are set, it will call into them on all but the initial serialization to perform delta serialization. (This could be useful if you have a large struct where most values do not change regularly and you want to send only the fields that did change.) (#2813) + +### Fixed + +- Fixed issue where `NetworkTransformEditor` would throw and exception if you excluded the physics package. (#2871) +- Fixed issue where `NetworkTransform` could not properly synchronize its base position when using half float precision. (#2845) +- Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822) +- Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807) +- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) + +### Changed + +- Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2874) +- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2872) +- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) +- Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807) + ## [1.8.1] - 2024-02-05 ### Fixed diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index ce0f648ace..5b53f1a28e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -660,16 +660,69 @@ internal void UpdateNetworkProperties() /// the future network tick that the will be despawned on non-authoritative instances public virtual void OnDeferringDespawn(int despawnTick) { } + /// Gets called after the is spawned. No NetworkBehaviours associated with the NetworkObject will have had invoked yet. + /// A reference to is passed in as a parameter to determine the context of execution (IsServer/IsClient) + /// + /// + /// a ref to the since this is not yet set on the + /// The will not have anything assigned to it at this point in time. + /// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn related properties will not be set. + /// This can be used to handle things like initializing/instantiating a NetworkVariable or the like. + /// + protected virtual void OnNetworkPreSpawn(ref NetworkManager networkManager) { } + /// /// Gets called when the gets spawned, message handlers are ready to be registered and the network is setup. /// public virtual void OnNetworkSpawn() { } + /// + /// Gets called after the is spawned. All NetworkBehaviours associated with the NetworkObject will have had invoked. + /// + /// + /// Will be invoked on each associated with the being spawned. + /// All associated components will have had invoked on the spawned . + /// + protected virtual void OnNetworkPostSpawn() { } + + /// + /// [Client-Side Only] + /// When a new client joins it is synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all + /// s and scenes (if scene management is enabled) have finished synchronizing, all NetworkBehaviour components associated with spawned s + /// will have this method invoked. + /// + /// + /// This can be used to handle post synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. + /// This is only invoked on clients during a client-server network topology session. + /// + protected virtual void OnNetworkSessionSynchronized() { } + + /// + /// [Client & Server Side] + /// When a scene is loaded an in-scene placed NetworkObjects are all spawned, this method is invoked on all of the newly spawned in-scene placed NetworkObjects. + /// + /// + /// This can be used to handle post scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. + /// + protected virtual void OnInSceneObjectsSpawned() { } + /// /// Gets called when the gets despawned. Is called both on the server and clients. /// public virtual void OnNetworkDespawn() { } + internal void NetworkPreSpawn(ref NetworkManager networkManager) + { + try + { + OnNetworkPreSpawn(ref networkManager); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + internal void InternalOnNetworkSpawn() { IsSpawned = true; @@ -699,6 +752,42 @@ internal void VisibleOnNetworkSpawn() } } + internal void NetworkPostSpawn() + { + try + { + OnNetworkPostSpawn(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + internal void NetworkSessionSynchronized() + { + try + { + OnNetworkSessionSynchronized(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + internal void InSceneNetworkObjectsSpawned() + { + try + { + OnInSceneObjectsSpawned(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + internal void InternalOnNetworkDespawn() { IsSpawned = false; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 7aa3a72026..7553aa24f2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2209,6 +2209,18 @@ internal static void CheckOrphanChildren() } } + internal void InvokeBehaviourNetworkPreSpawn() + { + var networkManager = NetworkManager; + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager); + } + } + } + internal void InvokeBehaviourNetworkSpawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); @@ -2238,6 +2250,42 @@ internal void InvokeBehaviourNetworkSpawn() } } + internal void InvokeBehaviourNetworkPostSpawn() + { + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].NetworkPostSpawn(); + } + } + } + + + internal void InternalNetworkSessionSynchronized() + { + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].NetworkSessionSynchronized(); + } + } + } + + internal void InternalInSceneNetworkObjectsSpawned() + { + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].InSceneNetworkObjectsSpawned(); + } + } + } + + + internal void InvokeBehaviourNetworkDespawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); @@ -2862,6 +2910,9 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf // in order to be able to determine which NetworkVariables the client will be allowed to read. networkObject.OwnerClientId = sceneObject.OwnerClientId; + // Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours + networkObject.InvokeBehaviourNetworkPreSpawn(); + // Synchronize NetworkBehaviours var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId); @@ -3051,6 +3102,7 @@ internal static void UpdateNetworkObjectSceneChanges() private void Awake() { + m_ChildNetworkBehaviours = null; SetCachedParent(transform.parent); SceneOrigin = gameObject.scene; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 174eba3ee1..3c1b9cb4cc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -222,6 +222,12 @@ public void Handle(ref NetworkContext context) } // When scene management is disabled we notify after everything is synchronized networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); + + // For convenience, notify all NetworkBehaviours that synchronization is complete. + foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) + { + networkObject.InternalNetworkSessionSynchronized(); + } } else { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 1e6dacfd84..02aab36c6e 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1859,6 +1859,17 @@ private void OnSessionOwnerLoadedScene(uint sceneEventId, Scene scene) } } + foreach (var keyValuePairByGlobalObjectIdHash in ScenePlacedObjects) + { + foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value) + { + if (!keyValuePairBySceneHandle.Value.IsPlayerObject) + { + keyValuePairBySceneHandle.Value.InternalInSceneNetworkObjectsSpawned(); + } + } + } + // Add any despawned when spawned in-scene placed NetworkObjects to the scene event data sceneEventData.AddDespawnedInSceneNetworkObjects(); @@ -2413,6 +2424,12 @@ private void HandleClientSceneEvent(uint sceneEventId) { NetworkLog.LogInfo($"[Client-{NetworkManager.LocalClientId}][Scene Management Enabled] Synchronization complete!"); } + // For convenience, notify all NetworkBehaviours that synchronization is complete. + foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) + { + networkObject.InternalNetworkSessionSynchronized(); + } + EndSceneEvent(sceneEventId); } break; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index f0293e4f98..18ac6a7ed1 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -839,7 +839,7 @@ internal void DeserializeScenePlacedObjects() { // is not packed! InternalBuffer.ReadValueSafe(out ushort newObjectsCount); - + var sceneObjects = new List(); for (ushort i = 0; i < newObjectsCount; i++) { var sceneObject = new NetworkObject.SceneObject(); @@ -851,10 +851,22 @@ internal void DeserializeScenePlacedObjects() m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle); } - NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager); + var networkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager); + + if (sceneObject.IsSceneObject) + { + sceneObjects.Add(networkObject); + } } // Now deserialize the despawned in-scene placed NetworkObjects list (if any) DeserializeDespawnedInScenePlacedNetworkObjects(); + + // Notify all newly spawned in-scene placed NetworkObjects that all in-scene placed + // NetworkObjects have been spawned. + foreach (var networkObject in sceneObjects) + { + networkObject.InternalInSceneNetworkObjectsSpawned(); + } } finally { @@ -1122,6 +1134,15 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) UnityEngine.Debug.Log(builder.ToString()); } + // Notify that all in-scene placed NetworkObjects have been spawned + foreach (var networkObject in m_NetworkObjectsSync) + { + if (networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value) + { + networkObject.InternalInSceneNetworkObjectsSpawned(); + } + } + // Now deserialize the despawned in-scene placed NetworkObjects list (if any) DeserializeDespawnedInScenePlacedNetworkObjects(); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 210c249cde..dcd7b8ba7a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -924,6 +924,8 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo Debug.LogError("Spawning NetworkObjects with nested NetworkObjects is only supported for scene objects. Child NetworkObjects will not be spawned over the network!"); } } + // Invoke NetworkBehaviour.OnPreSpawn methods + networkObject.InvokeBehaviourNetworkPreSpawn(); // DANGO-TODO: It would be nice to allow users to specify which clients are observers prior to spawning // For now, this is the best place I could find to add all connected clients as observers for newly @@ -964,12 +966,18 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong netwo } } SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene); + + // Invoke NetworkBehaviour.OnPostSpawn methods + networkObject.InvokeBehaviourNetworkPostSpawn(); } /// /// This is only invoked to instantiate a serialized NetworkObject via /// /// + /// + /// IMPORTANT: Pre spawn methods need to be invoked from within . + /// internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene) { if (networkObject == null) @@ -982,7 +990,11 @@ internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkO throw new SpawnStateException($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!"); } + // Do not invoke Pre spawn here (SynchronizeNetworkBehaviours needs to be invoked prior to this) SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene); + + // It is ok to invoke NetworkBehaviour.OnPostSpawn methods + networkObject.InvokeBehaviourNetworkPostSpawn(); } private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) @@ -1314,6 +1326,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep() var networkObjects = UnityEngine.Object.FindObjectsOfType(); #endif var isConnectedCMBService = NetworkManager.CMBServiceConnection; + var networkObjectsToSpawn = new List(); for (int i = 0; i < networkObjects.Length; i++) { if (networkObjects[i].NetworkManager == NetworkManager) @@ -1330,9 +1343,17 @@ internal void ServerSpawnSceneObjectsOnStartSweep() } SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true); + networkObjectsToSpawn.Add(networkObjects[i]); } } } + + // Notify all in-scene placed NetworkObjects have been spawned + foreach (var networkObject in networkObjectsToSpawn) + { + networkObject.InternalInSceneNetworkObjectsSpawned(); + } + networkObjectsToSpawn.Clear(); } internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs new file mode 100644 index 0000000000..ec5941aaa9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs @@ -0,0 +1,143 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + public class NetworkBehaviourPrePostSpawnTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + private bool m_AllowServerToStart; + + private GameObject m_PrePostSpawnObject; + + public NetworkBehaviourPrePostSpawnTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + protected override void OnServerAndClientsCreated() + { + m_PrePostSpawnObject = CreateNetworkObjectPrefab("PrePostSpawn"); + // Reverse the order of the components to get inverted spawn sequence + m_PrePostSpawnObject.AddComponent(); + m_PrePostSpawnObject.AddComponent(); + base.OnServerAndClientsCreated(); + } + + public class NetworkBehaviourPreSpawn : NetworkBehaviour + { + public static int ValueToSet; + public bool OnNetworkPreSpawnCalled; + public bool NetworkVarValueMatches; + + public NetworkVariable TestNetworkVariable; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + OnNetworkPreSpawnCalled = true; + // If we are the server, then set the randomly generated value (1-200). + // Otherwise, just set the value to 0. + // TODO: Make adjustments when integrated CMB service testing is added + var val = networkManager.IsServer ? ValueToSet : 0; + // Instantiate the NetworkVariable as everyone read & owner write while also setting the value + TestNetworkVariable = new NetworkVariable(val, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + base.OnNetworkPreSpawn(ref networkManager); + } + + public override void OnNetworkSpawn() + { + // For both client and server this should match at this point + NetworkVarValueMatches = TestNetworkVariable.Value == ValueToSet; + base.OnNetworkSpawn(); + } + } + + public class NetworkBehaviourPostSpawn : NetworkBehaviour + { + public bool OnNetworkPostSpawnCalled; + + private NetworkBehaviourPreSpawn m_NetworkBehaviourPreSpawn; + + public int ValueSet; + + public override void OnNetworkSpawn() + { + // Obtain the NetworkBehaviourPreSpawn component + // (could also do this during OnNetworkPreSpawn if we wanted) + m_NetworkBehaviourPreSpawn = GetComponent(); + base.OnNetworkSpawn(); + } + + protected override void OnNetworkPostSpawn() + { + OnNetworkPostSpawnCalled = true; + // We should be able to access the component we got during OnNetworkSpawn and all values should be set + // (i.e. OnNetworkSpawn run on all NetworkObject relative NetworkBehaviours) + ValueSet = m_NetworkBehaviourPreSpawn.TestNetworkVariable.Value; + base.OnNetworkPostSpawn(); + } + + } + + protected override bool CanStartServerAndClients() + { + return m_AllowServerToStart; + } + + protected override IEnumerator OnSetup() + { + m_AllowServerToStart = false; + return base.OnSetup(); + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; + base.OnNewClientCreated(networkManager); + } + + /// + /// This validates that pre spawn can be used to instantiate and assign a NetworkVariable (or other prespawn tasks) + /// which can be useful for assigning a NetworkVariable value on the server side when the NetworkVariable has owner write permissions. + /// This also assures that duruing post spawn all associated NetworkBehaviours have run through the OnNetworkSpawn pass (i.e. OnNetworkSpawn order is not an issue) + /// + [UnityTest] + public IEnumerator OnNetworkPreAndPostSpawn() + { + m_AllowServerToStart = true; + NetworkBehaviourPreSpawn.ValueToSet = Random.Range(1, 200); + yield return StartServerAndClients(); + + yield return CreateAndStartNewClient(); + + // Spawn the object with the newly joined client as the owner + var networkManager = m_DistributedAuthority ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; + var authorityInstance = SpawnObject(m_PrePostSpawnObject, networkManager); + var authorityNetworkObject = authorityInstance.GetComponent(); + var authorityPreSpawn = authorityInstance.GetComponent(); + var authorityPostSpawn = authorityInstance.GetComponent(); + + yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClientId) + && s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId].ContainsKey(authorityNetworkObject.NetworkObjectId)); + AssertOnTimeout($"Client-{m_ClientNetworkManagers[0].LocalClientId} failed to spawn {nameof(NetworkObject)} id-{authorityNetworkObject.NetworkObjectId}!"); + + var clientNetworkObject = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][authorityNetworkObject.NetworkObjectId]; + var clientPreSpawn = clientNetworkObject.GetComponent(); + var clientPostSpawn = clientNetworkObject.GetComponent(); + + Assert.IsTrue(authorityPreSpawn.OnNetworkPreSpawnCalled, $"[Authority-side] OnNetworkPreSpawn not invoked!"); + Assert.IsTrue(clientPreSpawn.OnNetworkPreSpawnCalled, $"[Client-side] OnNetworkPreSpawn not invoked!"); + Assert.IsTrue(authorityPostSpawn.OnNetworkPostSpawnCalled, $"[Authority-side] OnNetworkPostSpawn not invoked!"); + Assert.IsTrue(clientPostSpawn.OnNetworkPostSpawnCalled, $"[Client-side] OnNetworkPostSpawn not invoked!"); + + Assert.IsTrue(authorityPreSpawn.NetworkVarValueMatches, $"[Authority-side][PreSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {authorityPreSpawn.TestNetworkVariable.Value}!"); + Assert.IsTrue(clientPreSpawn.NetworkVarValueMatches, $"[Client-side][PreSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {clientPreSpawn.TestNetworkVariable.Value}!"); + + Assert.IsTrue(authorityPostSpawn.ValueSet == NetworkBehaviourPreSpawn.ValueToSet, $"[Authority-side][PostSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {authorityPostSpawn.ValueSet}!"); + Assert.IsTrue(clientPostSpawn.ValueSet == NetworkBehaviourPreSpawn.ValueToSet, $"[Client-side][PostSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {clientPostSpawn.ValueSet}!"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs.meta new file mode 100644 index 0000000000..70ad282a23 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2263d66f6df15a7428d279dbdaba1519 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs b/testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs new file mode 100644 index 0000000000..5d141c5d95 --- /dev/null +++ b/testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs @@ -0,0 +1,62 @@ +using Unity.Netcode; +using UnityEngine; + +namespace TestProject.ManualTests +{ + public class SessionSynchronizedTest : NetworkBehaviour + { + public static SessionSynchronizedTest FirstObject; + public static SessionSynchronizedTest SecondObject; + public SessionSynchronizedTest ObjectForServerToReference; + public NetworkVariable OtherObject = new NetworkVariable(); + public bool IsFirstObject; + public bool OnInSceneObjectsSpawnedInvoked; + + [Range(1, 1000)] + public int ValueToCheck; + + public int OtherValueObtained; + + public SessionSynchronizedTest ClientSideReferencedBehaviour; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + if (!networkManager.IsServer) + { + if (IsFirstObject) + { + FirstObject = this; + } + else + { + SecondObject = this; + } + } + base.OnNetworkPreSpawn(ref networkManager); + } + + protected override void OnNetworkSessionSynchronized() + { + if (!HasAuthority) + { + OtherObject.Value.TryGet(out ClientSideReferencedBehaviour, NetworkManager); + OtherValueObtained = ClientSideReferencedBehaviour.ValueToCheck; + } + base.OnNetworkSessionSynchronized(); + } + + /// + /// Tests the in-scene objects spawned method gets invoked after a scene has been loaded and the associated in-scene placed NetworkObjects + /// have been spawned. + /// + protected override void OnInSceneObjectsSpawned() + { + if (HasAuthority) + { + OtherObject.Value = new NetworkBehaviourReference(ObjectForServerToReference); + } + OnInSceneObjectsSpawnedInvoked = true; + base.OnInSceneObjectsSpawned(); + } + } +} diff --git a/testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs.meta b/testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs.meta new file mode 100644 index 0000000000..a112fbb3ec --- /dev/null +++ b/testproject/Assets/Tests/Manual/HybridScripts/SessionSynchronizedTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 231239ca566ad7e4da43fd99c4601378 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity new file mode 100644 index 0000000000..3be955c9a5 --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity @@ -0,0 +1,271 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.37311953, g: 0.38074014, b: 0.3587274, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &470552562 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 470552565} + - component: {fileID: 470552563} + - component: {fileID: 470552564} + m_Layer: 0 + m_Name: FirstInSceneObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &470552563 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 470552562} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 99604880 + InScenePlacedSourceGlobalObjectIdHash: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!114 &470552564 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 470552562} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 231239ca566ad7e4da43fd99c4601378, type: 3} + m_Name: + m_EditorClassIdentifier: + ObjectForServerToReference: {fileID: 1617529774} + IsFirstObject: 1 + ValueToCheck: 250 + OtherValueObtained: 0 +--- !u!4 &470552565 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 470552562} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1617529773 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1617529775} + - component: {fileID: 1617529776} + - component: {fileID: 1617529774} + m_Layer: 0 + m_Name: SecondInSceneObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1617529774 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1617529773} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 231239ca566ad7e4da43fd99c4601378, type: 3} + m_Name: + m_EditorClassIdentifier: + ObjectForServerToReference: {fileID: 470552564} + IsFirstObject: 0 + ValueToCheck: 600 + OtherValueObtained: 0 +--- !u!4 &1617529775 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1617529773} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1617529776 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1617529773} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 3716379494 + InScenePlacedSourceGlobalObjectIdHash: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 470552565} + - {fileID: 1617529775} diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity.meta new file mode 100644 index 0000000000..d86acbce17 --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 468b795904b98234593ebc31bf0d578a +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs new file mode 100644 index 0000000000..714b87b88b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs @@ -0,0 +1,51 @@ +using System.Collections; +using NUnit.Framework; +using TestProject.ManualTests; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + public class NetworkBehaviourSessionSynchronized : NetcodeIntegrationTest + { + private const string k_SceneToLoad = "SessionSynchronize"; + protected override int NumberOfClients => 0; + + private bool m_SceneLoaded; + + public NetworkBehaviourSessionSynchronized(HostOrServer hostOrServer) : base(hostOrServer) { } + + [UnityTest] + public IEnumerator InScenePlacedSessionSynchronized() + { + m_SceneLoaded = false; + m_ServerNetworkManager.SceneManager.OnSceneEvent += OnSceneEvent; + m_ServerNetworkManager.SceneManager.LoadScene(k_SceneToLoad, UnityEngine.SceneManagement.LoadSceneMode.Additive); + yield return WaitForConditionOrTimeOut(() => m_SceneLoaded); + AssertOnTimeout($"Timed out waiting for scene {k_SceneToLoad} to load!"); + yield return CreateAndStartNewClient(); + AssertOnTimeout($"Timed out waiting for client to join session!"); + var firstObject = SessionSynchronizedTest.FirstObject; + var secondObject = SessionSynchronizedTest.SecondObject; + + Assert.True(firstObject == secondObject.ClientSideReferencedBehaviour); + Assert.True(secondObject == firstObject.ClientSideReferencedBehaviour); + Assert.True(firstObject.OtherValueObtained == secondObject.ValueToCheck); + Assert.True(secondObject.OtherValueObtained == firstObject.ValueToCheck); + Assert.True(firstObject.OnInSceneObjectsSpawnedInvoked); + Assert.True(secondObject.OnInSceneObjectsSpawnedInvoked); + } + + private void OnSceneEvent(SceneEvent sceneEvent) + { + if (sceneEvent.ClientId == m_ServerNetworkManager.LocalClientId && sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted) + { + m_SceneLoaded = true; + m_ServerNetworkManager.SceneManager.OnSceneEvent -= OnSceneEvent; + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs.meta b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs.meta new file mode 100644 index 0000000000..32daf28be5 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3bc1044db79cdcb41bc37e7e906e966f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index 6d435293c4..ea315828a3 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -149,6 +149,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/IntegrationTestScenes/InSceneUnderGameObjectWithNT.unity guid: 5a4f489df08d16c4d8c0167b099de2ca + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity + guid: 468b795904b98234593ebc31bf0d578a m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} From f6f815c99e8f12649200c4333b3db26c3f52f7d7 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 10 May 2024 12:43:18 -0500 Subject: [PATCH 047/236] chore!: migrate components to runtime assembly [MTT-8472] (#2914) * update Migrated components to runtime. Added some low hanging fruit updates to NetworkTransform and NetworkTransformMessage to help optimize cpu consumption and reduce GC allocations. Disabled the ticks ago since RTT seems to only update when a client sends packets. * test Made adjustments to tests and testproject assets. * fix Fixing scenario where m_UseRigidbodyForMotion is not defined and was being used to register for updates. Removed some whitespaces. Wrapping all fixed update related methods and properties in NetworkManager within #if COM_UNITY_MODULES_PHYSICS. Adjusting the ILPP for NetworkBehaviour to scan the Runtime assembly too. Adjusting the early exit for lack of observer count to only happen in DA mode. * test fix Sometimes the ClientDisconnectMultipleClients can exceed more than 2s (but seemingly less than 5) before receiving all events. Reverting the OnUpdate change to TransformInterpolationObject's back to Update as both instances need to update regularly for this test. Fixing issue with unreliable delta full state synchronization. --- .../Components/AssemblyInfo.cs | 15 -- .../Components/AssemblyInfo.cs.meta | 11 - .../Messages/NetworkTransformMessage.cs | 205 ------------------ .../com.unity.netcode.components.asmdef | 27 --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 3 +- .../Editor/NetworkManagerEditor.cs | 18 ++ .../Components.meta} | 5 +- .../{ => Runtime}/Components/HalfVector3.cs | 0 .../Components/HalfVector3.cs.meta | 0 .../{ => Runtime}/Components/HalfVector4.cs | 0 .../Components/HalfVector4.cs.meta | 0 .../Components/Interpolator.meta | 0 .../BufferedLinearInterpolator.cs | 0 .../BufferedLinearInterpolator.cs.meta | 0 .../{ => Runtime}/Components/Messages.meta | 0 .../Components/NetworkAnimator.cs | 0 .../Components/NetworkAnimator.cs.meta | 0 .../Components/NetworkDeltaPosition.cs | 8 +- .../Components/NetworkDeltaPosition.cs.meta | 0 .../Components/NetworkRigidBodyBase.cs | 17 -- .../Components/NetworkRigidBodyBase.cs.meta | 0 .../Components/NetworkRigidbody.cs | 0 .../Components/NetworkRigidbody.cs.meta | 0 .../Components/NetworkRigidbody2D.cs | 0 .../Components/NetworkRigidbody2D.cs.meta | 0 .../Components/NetworkTransform.cs | 185 +++++++++------- .../Components/NetworkTransform.cs.meta | 0 .../Components/QuaternionCompressor.cs | 0 .../Components/QuaternionCompressor.cs.meta | 0 .../RigidbodyContactEventManager.cs | 0 .../RigidbodyContactEventManager.cs.meta | 0 .../Runtime/Configuration/NetworkConfig.cs | 6 + .../Runtime/Core/NetworkManager.cs | 78 ++++++- .../Runtime/Core/NetworkObject.cs | 8 + .../Messages/ChangeOwnershipMessage.cs | 4 +- .../Messaging/Messages/CreateObjectMessage.cs | 4 +- .../Messages/DestroyObjectMessage.cs | 4 +- .../Messages/NetworkTransformMessage.cs | 190 ++++++++++++++++ .../Messages/NetworkTransformMessage.cs.meta | 0 .../Messages/NetworkVariableDeltaMessage.cs | 4 +- .../Messaging/Messages/ParentSyncMessage.cs | 4 +- .../Runtime/Messaging/Messages/RpcMessages.cs | 12 +- .../Runtime/com.unity.netcode.runtime.asmdef | 15 ++ .../DistributeObjectsTests.cs | 4 +- .../Runtime/TransformInterpolationTests.cs | 4 +- .../UnityTransportConnectionTests.cs | 2 +- .../AutomatedPlayerMover.cs | 4 +- 47 files changed, 462 insertions(+), 375 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Components/AssemblyInfo.cs delete mode 100644 com.unity.netcode.gameobjects/Components/AssemblyInfo.cs.meta delete mode 100644 com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs delete mode 100644 com.unity.netcode.gameobjects/Components/com.unity.netcode.components.asmdef rename com.unity.netcode.gameobjects/{Components/com.unity.netcode.components.asmdef.meta => Runtime/Components.meta} (57%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/HalfVector3.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/HalfVector3.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/HalfVector4.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/HalfVector4.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/Interpolator.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/Interpolator/BufferedLinearInterpolator.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/Interpolator/BufferedLinearInterpolator.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/Messages.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkAnimator.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkAnimator.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkDeltaPosition.cs (97%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkDeltaPosition.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkRigidBodyBase.cs (95%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkRigidBodyBase.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkRigidbody.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkRigidbody.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkRigidbody2D.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkRigidbody2D.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkTransform.cs (96%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/NetworkTransform.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/QuaternionCompressor.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/QuaternionCompressor.cs.meta (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/RigidbodyContactEventManager.cs (100%) rename com.unity.netcode.gameobjects/{ => Runtime}/Components/RigidbodyContactEventManager.cs.meta (100%) create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs rename com.unity.netcode.gameobjects/{Components => Runtime/Messaging}/Messages/NetworkTransformMessage.cs.meta (100%) diff --git a/com.unity.netcode.gameobjects/Components/AssemblyInfo.cs b/com.unity.netcode.gameobjects/Components/AssemblyInfo.cs deleted file mode 100644 index dca734793c..0000000000 --- a/com.unity.netcode.gameobjects/Components/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Runtime.CompilerServices; - -#if UNITY_EDITOR -[assembly: InternalsVisibleTo("Unity.Netcode.Editor")] -[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")] -#endif // UNITY_EDITOR - -#if UNITY_INCLUDE_TESTS -[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] -[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] -#if UNITY_EDITOR -[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")] -[assembly: InternalsVisibleTo("TestProject.EditorTests")] -#endif // UNITY_EDITOR -#endif // UNITY_INCLUDE_TESTS diff --git a/com.unity.netcode.gameobjects/Components/AssemblyInfo.cs.meta b/com.unity.netcode.gameobjects/Components/AssemblyInfo.cs.meta deleted file mode 100644 index 36524ba59b..0000000000 --- a/com.unity.netcode.gameobjects/Components/AssemblyInfo.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5b8086dc75d86473f9e3c928dd773733 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs deleted file mode 100644 index 28a423ee9a..0000000000 --- a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs +++ /dev/null @@ -1,205 +0,0 @@ -using Unity.Netcode.Components; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// NetworkTransform State Update Message - /// - internal struct NetworkTransformMessage : INetworkMessage - { - public int Version => 0; - public ulong NetworkObjectId; - public int NetworkBehaviourId; - // This is only used when serializing but not serialized - public bool DistributedAuthorityMode; - // Might get removed - public ulong[] TargetIds; - - private int GetTargetIdLength() - { - if (TargetIds != null) - { - return TargetIds.Length; - } - return 0; - } - - public NetworkTransform.NetworkTransformState State; - - private NetworkTransform m_ReceiverNetworkTransform; - private FastBufferReader m_CurrentReader; - - private unsafe void CopyPayload(ref FastBufferWriter writer) - { - writer.WriteBytesSafe(m_CurrentReader.GetUnsafePtrAtCurrentPosition(), m_CurrentReader.Length - m_CurrentReader.Position); - } - - public void Serialize(FastBufferWriter writer, int targetVersion) - { - if (m_CurrentReader.IsInitialized) - { - CopyPayload(ref writer); - } - else - { - BytePacker.WriteValueBitPacked(writer, NetworkObjectId); - BytePacker.WriteValueBitPacked(writer, NetworkBehaviourId); - writer.WriteNetworkSerializable(State); - if (DistributedAuthorityMode) - { - var length = GetTargetIdLength(); - BytePacker.WriteValuePacked(writer, length); - // If no target ids, then just exit early (DAHost specific) - if (length == 0) - { - return; - } - foreach (var target in TargetIds) - { - BytePacker.WriteValuePacked(writer, target); - } - } - } - } - - public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) - { - var networkManager = context.SystemOwner as NetworkManager; - if (networkManager == null) - { - Debug.LogError($"[{nameof(NetworkTransformMessage)}] System owner context was not of type {nameof(NetworkManager)}!"); - return false; - } - var currentPosition = reader.Position; - ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); - var isSpawnedLocally = networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId); - - // Only defer if the NetworkObject is not spawned yet and the local NetworkManager is not running as a DAHost. - if (!isSpawnedLocally && !networkManager.DAHost) - { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); - return false; - } - - // While the below check and assignment might seem out of place, this is specific to running in DAHost mode when a NetworkObject is - // hidden from the DAHost but is visible to other clients. Since the DAHost needs to forward updates to the clients, we ignore processing - // this message locally - var networkObject = (NetworkObject)null; - var isServerAuthoritative = false; - var ownerAuthoritativeServerSide = false; - - // Get the behaviour index - ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourId); - - // Deserialize the state - reader.ReadNetworkSerializableInPlace(ref State); - - if (networkManager.DistributedAuthorityMode) - { - var targetCount = 0; - ByteUnpacker.ReadValueBitPacked(reader, out targetCount); - if (targetCount > 0) - { - TargetIds = new ulong[targetCount]; - } - var targetId = (ulong)0; - for (int i = 0; i < targetCount; i++) - { - ByteUnpacker.ReadValueBitPacked(reader, out targetId); - TargetIds[i] = targetId; - } - } - - if (isSpawnedLocally) - { - networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; - // Get the target NetworkTransform - m_ReceiverNetworkTransform = networkObject.ChildNetworkBehaviours[NetworkBehaviourId] as NetworkTransform; - isServerAuthoritative = m_ReceiverNetworkTransform.IsServerAuthoritative(); - ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer; - } - else - { - // If we are the DAHost and the NetworkObject is hidden from the host we still need to forward this message - ownerAuthoritativeServerSide = networkManager.DAHost && !isSpawnedLocally; - } - - if (ownerAuthoritativeServerSide) - { - var ownerClientId = (ulong)0; - - if (networkObject != null) - { - ownerClientId = networkObject.OwnerClientId; - if (ownerClientId == NetworkManager.ServerClientId) - { - // Ownership must have changed, ignore any additional pending messages that might have - // come from a previous owner client. - return true; - } - } - else if (networkManager.DAHost) - { - // Specific to distributed authority mode, the only sender of state updates will be the owner - ownerClientId = context.SenderId; - } - - var networkDelivery = State.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; - - // Forward the state update if there are any remote clients to foward it to - if (networkManager.ConnectionManager.ConnectedClientsList.Count > (networkManager.IsHost ? 2 : 1)) - { - var clientCount = networkManager.DistributedAuthorityMode ? GetTargetIdLength() : networkManager.ConnectionManager.ConnectedClientsList.Count; - if (clientCount == 0) - { - return true; - } - - // This is only to copy the existing and already serialized struct for forwarding purposes only. - // This will not include any changes made to this struct at this particular stage of processing the message. - var currentMessage = this; - // Create a new reader that replicates this message - currentMessage.m_CurrentReader = new FastBufferReader(reader, Collections.Allocator.None); - // Rewind the new reader to the beginning of the message's payload - currentMessage.m_CurrentReader.Seek(currentPosition); - // Forward the message to all connected clients that are observers of the associated NetworkObject - - for (int i = 0; i < clientCount; i++) - { - var clientId = networkManager.DistributedAuthorityMode ? TargetIds[i] : networkManager.ConnectionManager.ConnectedClientsList[i].ClientId; - if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) || - (!networkManager.DistributedAuthorityMode && !networkObject.Observers.Contains(clientId))) - { - continue; - } - - networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); - } - // Dispose of the reader used for forwarding - currentMessage.m_CurrentReader.Dispose(); - } - } - return true; - } - - public void Handle(ref NetworkContext context) - { - var networkManager = context.SystemOwner as NetworkManager; - // Only if the local NetworkManager instance is running as the DAHost we just exit if there is no local - // NetworkTransform component to apply the state update to (i.e. it is hidden from the DAHost and it - // just forwarded the state update to any other connected client) - if (networkManager.DAHost && m_ReceiverNetworkTransform == null) - { - return; - } - - if (m_ReceiverNetworkTransform == null) - { - Debug.LogError($"[{nameof(NetworkTransformMessage)}][Dropped] Reciever {nameof(NetworkTransform)} was not set!"); - return; - } - m_ReceiverNetworkTransform.TransformStateUpdate(ref State, context.SenderId); - } - } -} diff --git a/com.unity.netcode.gameobjects/Components/com.unity.netcode.components.asmdef b/com.unity.netcode.gameobjects/Components/com.unity.netcode.components.asmdef deleted file mode 100644 index 3505da5d1a..0000000000 --- a/com.unity.netcode.gameobjects/Components/com.unity.netcode.components.asmdef +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "Unity.Netcode.Components", - "rootNamespace": "Unity.Netcode.Components", - "references": [ - "Unity.Netcode.Runtime", - "Unity.Collections", - "Unity.Mathematics" - ], - "allowUnsafeCode": true, - "versionDefines": [ - { - "name": "com.unity.modules.animation", - "expression": "", - "define": "COM_UNITY_MODULES_ANIMATION" - }, - { - "name": "com.unity.modules.physics", - "expression": "", - "define": "COM_UNITY_MODULES_PHYSICS" - }, - { - "name": "com.unity.modules.physics2d", - "expression": "", - "define": "COM_UNITY_MODULES_PHYSICS2D" - } - ] -} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 974b492e6c..f750f781c5 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -27,7 +27,8 @@ internal sealed class NetworkBehaviourILPP : ILPPInterface public override ILPPInterface GetInstance() => this; - public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); + public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName || + compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); private readonly List m_Diagnostics = new List(); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 011fe5d344..e35db5dbf0 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -43,6 +43,9 @@ public class NetworkManagerEditor : UnityEditor.Editor private SerializedProperty m_LoadSceneTimeOutProperty; private SerializedProperty m_PrefabsList; + private SerializedProperty m_NetworkProfileMetrics; + private SerializedProperty m_NetworkMessageMetrics; + private NetworkManager m_NetworkManager; private bool m_Initialized; @@ -109,6 +112,11 @@ private void Initialize() m_SpawnTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("SpawnTimeout"); m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); + m_NetworkProfileMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkProfileMetrics"); +#if MULTIPLAYER_TOOLS + m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics"); +#endif + m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty @@ -143,6 +151,11 @@ private void CheckNullProperties() m_SpawnTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("SpawnTimeout"); m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); + m_NetworkProfileMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkProfilingMetrics"); +#if MULTIPLAYER_TOOLS + m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics"); +#endif + m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty .FindPropertyRelative(nameof(NetworkConfig.Prefabs)) @@ -200,6 +213,11 @@ public override void OnInspectorGUI() } EditorGUILayout.PropertyField(m_RpcHashSizeProperty); + EditorGUILayout.PropertyField(m_NetworkProfileMetrics); +#if MULTIPLAYER_TOOLS + EditorGUILayout.PropertyField(m_NetworkMessageMetrics); +#endif + EditorGUILayout.Space(); EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty); diff --git a/com.unity.netcode.gameobjects/Components/com.unity.netcode.components.asmdef.meta b/com.unity.netcode.gameobjects/Runtime/Components.meta similarity index 57% rename from com.unity.netcode.gameobjects/Components/com.unity.netcode.components.asmdef.meta rename to com.unity.netcode.gameobjects/Runtime/Components.meta index 3dcf24db40..27ca3f3cd8 100644 --- a/com.unity.netcode.gameobjects/Components/com.unity.netcode.components.asmdef.meta +++ b/com.unity.netcode.gameobjects/Runtime/Components.meta @@ -1,6 +1,7 @@ fileFormatVersion: 2 -guid: 3b8ed52f1b5c64994af4c4e0aa4b6c4b -AssemblyDefinitionImporter: +guid: 2e42215d00468b549bbc69ebf8a74a1e +folderAsset: yes +DefaultImporter: externalObjects: {} userData: assetBundleName: diff --git a/com.unity.netcode.gameobjects/Components/HalfVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/HalfVector3.cs rename to com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs diff --git a/com.unity.netcode.gameobjects/Components/HalfVector3.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/HalfVector3.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/HalfVector4.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/HalfVector4.cs rename to com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs diff --git a/com.unity.netcode.gameobjects/Components/HalfVector4.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/HalfVector4.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/Interpolator.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/Interpolator.meta rename to com.unity.netcode.gameobjects/Runtime/Components/Interpolator.meta diff --git a/com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs rename to com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs diff --git a/com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/Messages.meta b/com.unity.netcode.gameobjects/Runtime/Components/Messages.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/Messages.meta rename to com.unity.netcode.gameobjects/Runtime/Components/Messages.meta diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkAnimator.cs rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs diff --git a/com.unity.netcode.gameobjects/Components/NetworkAnimator.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkAnimator.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs similarity index 97% rename from com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs index ec5c176874..2ed982d3e3 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs @@ -32,9 +32,13 @@ public struct NetworkDeltaPosition : INetworkSerializable ///
public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { - HalfVector3.NetworkSerialize(serializer); - if (SynchronizeBase) + if (!SynchronizeBase) { + HalfVector3.NetworkSerialize(serializer); + } + else + { + serializer.SerializeValue(ref DeltaPosition); serializer.SerializeValue(ref CurrentBasePosition); } } diff --git a/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs similarity index 95% rename from com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index d156314b2a..ea9ac67b68 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -512,23 +512,6 @@ public override void OnNetworkDespawn() } SetInterpolation(m_OriginalInterpolation); } - - /// - /// When is enabled, the will update Kinematic instances using - /// the Rigidbody's move methods allowing Rigidbody interpolation settings to be taken into consideration by the physics simulation. - /// - /// - /// This will update the associated during FixedUpdate which also avoids the added expense of adding - /// a FixedUpdate to all instances where some might not be using a Rigidbody. - /// - private void FixedUpdate() - { - if (!IsSpawned || m_NetworkTransform == null || !UseRigidBodyForMotion) - { - return; - } - m_NetworkTransform.OnFixedUpdate(); - } } } #endif // COM_UNITY_MODULES_PHYSICS diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkRigidBodyBase.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs diff --git a/com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs similarity index 96% rename from com.unity.netcode.gameobjects/Components/NetworkTransform.cs rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 2769b8db5f..cd1cce2f9e 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Text; using Unity.Mathematics; @@ -16,7 +15,6 @@ namespace Unity.Netcode.Components ///
[DisallowMultipleComponent] [AddComponentMenu("Netcode/Network Transform")] - [DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts public class NetworkTransform : NetworkBehaviour { #region NETWORK TRANSFORM STATE @@ -1186,12 +1184,6 @@ private bool SynchronizeScale ///
public bool CanCommitToTransform { get; protected set; } - /// - /// Internally used by to keep track of whether this derived class instance - /// was instantiated on the server side or not. - /// - protected bool m_CachedIsServer; // Note: we no longer use this and are only keeping it until we decide to deprecate it - /// /// Internally used by to keep track of the instance assigned to this /// this derived class instance. @@ -2856,12 +2848,6 @@ protected virtual void Awake() /// public override void OnNetworkSpawn() { - /////////////////////////////////////////////////////////////// - // NOTE: Legacy and no longer used (candidates for deprecation) - m_CachedIsServer = IsServer; - /////////////////////////////////////////////////////////////// - - // Started using this again to avoid the getter processing cost of NetworkBehaviour.NetworkManager m_CachedNetworkManager = NetworkManager; Initialize(); @@ -2874,6 +2860,13 @@ public override void OnNetworkSpawn() private void CleanUpOnDestroyOrDespawn() { + +#if COM_UNITY_MODULES_PHYSICS + var forUpdate = !m_UseRigidbodyForMotion; +#else + var forUpdate = true; +#endif + NetworkManager?.NetworkTransformRegistration(this, forUpdate, false); DeregisterForTickUpdate(this); CanCommitToTransform = false; } @@ -2932,7 +2925,7 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() m_ScaleInterpolator.ResetTo(transform.localScale, serverTime); m_RotationInterpolator.ResetTo(rotation, serverTime); } - + private NetworkObject m_CachedNetworkObject; /// /// The internal initialzation method to allow for internal API adjustments /// @@ -2943,7 +2936,7 @@ private void InternalInitialization(bool isOwnershipChange = false) { return; } - + m_CachedNetworkObject = NetworkObject; CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner; var currentPosition = GetSpaceRelativePosition(); var currentRotation = GetSpaceRelativeRotation(); @@ -2966,10 +2959,17 @@ private void InternalInitialization(bool isOwnershipChange = false) m_NetworkRigidbodyInternal.SetPosition(currentPosition); m_NetworkRigidbodyInternal.SetRotation(currentRotation); } + + var forUpdate = !m_UseRigidbodyForMotion; +#else + var forUpdate = true; #endif if (CanCommitToTransform) { + + // Make sure authority doesn't get added to updates (no need to do this on the authority side) + m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, false); if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); @@ -2987,6 +2987,8 @@ private void InternalInitialization(bool isOwnershipChange = false) } else { + // Non-authority needs to be added to updates for interpolation and applying state purposes + m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, true); // Remove this instance from the tick update DeregisterForTickUpdate(this); @@ -3261,11 +3263,12 @@ private void UpdateInterpolation() #else var ticksAgo = (!IsServerAuthoritative() && !IsServer) ? 2 : 1; #endif - if (m_CachedNetworkManager.DistributedAuthorityMode) - { - ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); - offset = m_NetworkTransformTickRegistration.Offset; - } + // TODO: We need an RTT that updates regularly and not only when the client sends packets + //if (m_CachedNetworkManager.DistributedAuthorityMode) + //{ + // ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); + // offset = m_NetworkTransformTickRegistration.Offset; + //} var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo, offset).Time; @@ -3296,7 +3299,7 @@ private void UpdateInterpolation() /// If you override this method, be sure that: /// - Non-authority always invokes this base class method. /// - protected virtual void Update() + public virtual void OnUpdate() { // If not spawned or this instance has authority, exit early #if COM_UNITY_MODULES_PHYSICS @@ -3321,7 +3324,7 @@ protected virtual void Update() /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . /// - internal void OnFixedUpdate() + public virtual void OnFixedUpdate() { // If not spawned or this instance has authority, exit early if (!m_UseRigidbodyForMotion || !IsSpawned || CanCommitToTransform) @@ -3367,11 +3370,21 @@ public bool IsServerAuthoritative() #endregion #region MESSAGE HANDLING + + internal NetworkTransformState InboundState = new NetworkTransformState(); + internal NetworkTransformState OutboundState + { + get + { + return m_LocalAuthoritativeNetworkState; + } + } + /// /// Invoked by to update the transform state /// /// - internal void TransformStateUpdate(ref NetworkTransformState networkTransformState, ulong senderId) + internal void TransformStateUpdate(ulong senderId) { if (CanCommitToTransform) { @@ -3383,18 +3396,43 @@ internal void TransformStateUpdate(ref NetworkTransformState networkTransformSta m_OldState = m_LocalAuthoritativeNetworkState; // Assign the new incoming state - m_LocalAuthoritativeNetworkState = networkTransformState; + m_LocalAuthoritativeNetworkState = InboundState; // Apply the state update OnNetworkStateChanged(m_OldState, m_LocalAuthoritativeNetworkState); } + // Used to send outbound messages + private NetworkTransformMessage m_OutboundMessage = new NetworkTransformMessage(); + + + internal void SerializeMessage(FastBufferWriter writer, int targetVersion) + { + var networkObject = NetworkObject; + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + BytePacker.WriteValueBitPacked(writer, (int)NetworkBehaviourId); + writer.WriteNetworkSerializable(m_LocalAuthoritativeNetworkState); + if (m_CachedNetworkManager.DistributedAuthorityMode) + { + BytePacker.WriteValuePacked(writer, networkObject.Observers.Count - 1); + + foreach (var targetId in networkObject.Observers) + { + if (OwnerClientId == targetId) + { + continue; + } + BytePacker.WriteValuePacked(writer, targetId); + } + } + } + /// /// Invoked by the authoritative instance to sends a containing the /// private void UpdateTransformState() { - if (m_CachedNetworkManager.ShutdownInProgress) + if (m_CachedNetworkManager.ShutdownInProgress || (m_CachedNetworkManager.DistributedAuthorityMode && m_CachedNetworkObject.Observers.Count - 1 == 0)) { return; } @@ -3409,17 +3447,7 @@ private void UpdateTransformState() Debug.LogError($"Owner authoritative {nameof(NetworkTransform)} can only be updated by the owner!"); } var customMessageManager = m_CachedNetworkManager.CustomMessagingManager; - - var networkTransformMessage = new NetworkTransformMessage() - { - NetworkObjectId = NetworkObjectId, - NetworkBehaviourId = NetworkBehaviourId, - State = m_LocalAuthoritativeNetworkState, - DistributedAuthorityMode = m_CachedNetworkManager.DistributedAuthorityMode, - // Don't populate if we are the DAHost as we send directly to each client - TargetIds = m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.DAHost ? NetworkObject.Observers.Where((c) => c != m_CachedNetworkManager.LocalClientId).ToArray() : null, - }; - + m_OutboundMessage.NetworkTransform = this; // Determine what network delivery method to use: // When to send reliable packets: @@ -3445,13 +3473,13 @@ private void UpdateTransformState() { continue; } - NetworkManager.MessageManager.SendMessage(ref networkTransformMessage, networkDelivery, clientId); + NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, clientId); } } else { // Clients (owner authoritative) send messages to the server-host - NetworkManager.MessageManager.SendMessage(ref networkTransformMessage, networkDelivery, NetworkManager.ServerClientId); + NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); } } #endregion @@ -3525,7 +3553,9 @@ public void Remove() internal float TicksAgoInSeconds() { - return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; + return 2 * m_TickFrequency; + // TODO: We need an RTT that updates regularly and not just when the client sends packets + //return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; } /// @@ -3534,25 +3564,26 @@ internal float TicksAgoInSeconds() /// private void TickUpdate() { - if (m_UnityTransport != null) - { - // Determine the desired ticks ago by the RTT (this really should be the combination of the - // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor - // network quality so latent interpolation is going to be expected). - var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); - m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); - var tickAgoSum = 0.0f; - foreach (var tickAgo in m_TicksAgoSamples) - { - tickAgoSum += tickAgo; - } - m_PreviousTicksAgo = TicksAgo; - TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); - m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; - // Get the partial tick value for when this is all calculated to provide an offset for determining - // the relative starting interpolation point for the next update - Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); - } + // TODO: We need an RTT that updates regularly and not just when the client sends packets + //if (m_UnityTransport != null) + //{ + // // Determine the desired ticks ago by the RTT (this really should be the combination of the + // // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor + // // network quality so latent interpolation is going to be expected). + // var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); + // m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); + // var tickAgoSum = 0.0f; + // foreach (var tickAgo in m_TicksAgoSamples) + // { + // tickAgoSum += tickAgo; + // } + // m_PreviousTicksAgo = TicksAgo; + // TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); + // m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; + // // Get the partial tick value for when this is all calculated to provide an offset for determining + // // the relative starting interpolation point for the next update + // Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); + //} // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before if (m_NetworkManager.ServerTime.Tick <= m_LastTick) @@ -3572,13 +3603,13 @@ private void TickUpdate() private UnityTransport m_UnityTransport; private float m_TickFrequency; - private float m_OffsetTickFrequency; - private ulong m_TickInMS; - private int m_TickSampleIndex; + //private float m_OffsetTickFrequency; + //private ulong m_TickInMS; + //private int m_TickSampleIndex; private int m_TickRate; public float TicksAgo { get; private set; } - public float Offset { get; private set; } - private float m_PreviousTicksAgo; + //public float Offset { get; private set; } + //private float m_PreviousTicksAgo; private List m_TicksAgoSamples = new List(); @@ -3589,19 +3620,19 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate; m_TickRate = (int)m_NetworkManager.NetworkConfig.TickRate; m_TickFrequency = 1.0f / m_TickRate; - // For the offset, it uses the fractional remainder of the tick to determine the offset. - // In order to keep within tick boundaries, we increment the tick rate by 1 to assure it - // will always be < the tick frequency. - m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); - m_TickInMS = (ulong)(1000 * m_TickFrequency); - m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; - // Fill the sample with a starting value of 1 - for (int i = 0; i < m_TickRate; i++) - { - m_TicksAgoSamples.Add(1f); - } - TicksAgo = 1f; - m_PreviousTicksAgo = 1f; + //// For the offset, it uses the fractional remainder of the tick to determine the offset. + //// In order to keep within tick boundaries, we increment the tick rate by 1 to assure it + //// will always be < the tick frequency. + //m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); + //m_TickInMS = (ulong)(1000 * m_TickFrequency); + //m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; + //// Fill the sample with a starting value of 1 + //for (int i = 0; i < m_TickRate; i++) + //{ + // m_TicksAgoSamples.Add(1f); + //} + TicksAgo = 2f; + //m_PreviousTicksAgo = 1f; if (networkManager.IsServer) { networkManager.OnServerStopped += OnNetworkManagerStopped; diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/NetworkTransform.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/QuaternionCompressor.cs b/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/QuaternionCompressor.cs rename to com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs diff --git a/com.unity.netcode.gameobjects/Components/QuaternionCompressor.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/QuaternionCompressor.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs.meta diff --git a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs similarity index 100% rename from com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs rename to com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs diff --git a/com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/RigidbodyContactEventManager.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index ff806c6354..91ab3bcede 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -158,6 +158,12 @@ public class NetworkConfig [Tooltip("When enabled (default), the player prefab will automatically be spawned (client-side) upon the client being approved and synchronized.")] public bool AutoSpawnPlayerPrefabClientSide = true; +#if MULTIPLAYER_TOOLS + public bool NetworkMessageMetrics = true; +#endif + + public bool NetworkProfilingMetrics = true; + /// /// Returns a base64 encoded version of the configuration /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 64637c8027..ba7c9f3198 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -8,6 +8,7 @@ #endif using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; +using Unity.Netcode.Components; namespace Unity.Netcode { @@ -177,6 +178,45 @@ public void PromoteSessionOwner(ulong clientId) } } + internal Dictionary NetworkTransformUpdate = new Dictionary(); +#if COM_UNITY_MODULES_PHYSICS + internal Dictionary NetworkTransformFixedUpdate = new Dictionary(); +#endif + + internal void NetworkTransformRegistration(NetworkTransform networkTransform, bool forUpdate = true, bool register = true) + { + if (forUpdate) + { + if (register) + { + if (!NetworkTransformUpdate.ContainsKey(networkTransform.NetworkObjectId)) + { + NetworkTransformUpdate.Add(networkTransform.NetworkObjectId, networkTransform); + } + } + else + { + NetworkTransformUpdate.Remove(networkTransform.NetworkObjectId); + } + } +#if COM_UNITY_MODULES_PHYSICS + else + { + if (register) + { + if (!NetworkTransformFixedUpdate.ContainsKey(networkTransform.NetworkObjectId)) + { + NetworkTransformFixedUpdate.Add(networkTransform.NetworkObjectId, networkTransform); + } + } + else + { + NetworkTransformFixedUpdate.Remove(networkTransform.NetworkObjectId); + } + } +#endif + } + public void NetworkUpdate(NetworkUpdateStage updateStage) { switch (updateStage) @@ -192,6 +232,19 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) MessageManager.CleanupDisconnectedClients(); } break; +#if COM_UNITY_MODULES_PHYSICS + case NetworkUpdateStage.FixedUpdate: + { + foreach (var networkTransformEntry in NetworkTransformFixedUpdate) + { + if (networkTransformEntry.Value.gameObject.activeInHierarchy && networkTransformEntry.Value.IsSpawned) + { + networkTransformEntry.Value.OnFixedUpdate(); + } + } + } + break; +#endif case NetworkUpdateStage.PreUpdate: { NetworkTimeSystem.UpdateTime(); @@ -205,6 +258,14 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) SpawnManager.DeferredDespawnUpdate(ServerTime); } + foreach (var networkTransformEntry in NetworkTransformUpdate) + { + if (networkTransformEntry.Value.gameObject.activeInHierarchy && networkTransformEntry.Value.IsSpawned) + { + networkTransformEntry.Value.OnUpdate(); + } + } + // Update any NetworkObject's registered to notify of scene migration changes. NetworkObject.UpdateNetworkObjectSceneChanges(); @@ -942,6 +1003,10 @@ public int MaximumFragmentedMessageSize internal void Initialize(bool server) { +#if COM_UNITY_MODULES_PHYSICS + NetworkTransformFixedUpdate.Clear(); +#endif + NetworkTransformUpdate.Clear(); //DANGOEXP TODO: Remove this before finalizing the experimental release NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode; @@ -978,6 +1043,9 @@ internal void Initialize(bool server) } this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate); +#if COM_UNITY_MODULES_PHYSICS + this.RegisterNetworkUpdate(NetworkUpdateStage.FixedUpdate); +#endif this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate); @@ -994,11 +1062,17 @@ internal void Initialize(bool server) MessageManager.Hook(new NetworkManagerHooks(this)); #if DEVELOPMENT_BUILD || UNITY_EDITOR - MessageManager.Hook(new ProfilingHooks()); + if (NetworkConfig.NetworkProfilingMetrics) + { + MessageManager.Hook(new ProfilingHooks()); + } #endif #if MULTIPLAYER_TOOLS - MessageManager.Hook(new MetricHooks(this)); + if (NetworkConfig.NetworkMessageMetrics) + { + MessageManager.Hook(new MetricHooks(this)); + } #endif // Assures there is a server message queue available diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 7553aa24f2..1df23e2f59 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using Unity.Netcode.Components; #if UNITY_EDITOR using UnityEditor; #if UNITY_2021_2_OR_NEWER @@ -55,6 +56,8 @@ public uint PrefabIdHash } } + public NetworkTransform NetworkTransform { get; private set; } + #if UNITY_EDITOR private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}"; @@ -2319,6 +2322,11 @@ internal List ChildNetworkBehaviours if (networkBehaviours[i].NetworkObject == this) { m_ChildNetworkBehaviours.Add(networkBehaviours[i]); + var type = networkBehaviours[i].GetType(); + if (type.IsInstanceOfType(typeof(NetworkTransform)) || type.IsSubclassOf(typeof(NetworkTransform))) + { + NetworkTransform = networkBehaviours[i] as NetworkTransform; + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 7ab41b8581..99010be29b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -5,6 +5,8 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem { public int Version => 0; + private const string k_Name = "ChangeOwnershipMessage"; + public ulong NetworkObjectId; public ulong OwnerClientId; // DANGOEXP TODO: Remove these notes or change their format @@ -199,7 +201,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int // authority of the NetworkObject in question. if (!networkManager.DAHost && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name); return false; } return true; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 6382cbb97a..6c0656b90a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -7,6 +7,8 @@ internal struct CreateObjectMessage : INetworkMessage { public int Version => 0; + private const string k_Name = "CreateObjectMessage"; + public NetworkObject.SceneObject ObjectInfo; private FastBufferReader m_ReceivedNetworkVariableData; @@ -161,7 +163,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo)) { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context, GetType().Name); + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context, k_Name); return false; } m_ReceivedNetworkVariableData = reader; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index b3d799a8e9..868d5a2540 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -6,6 +6,8 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp { public int Version => 0; + private const string k_Name = "DestroyObjectMessage"; + public ulong NetworkObjectId; public bool DestroyGameObject; private byte m_DestroyFlags; @@ -84,7 +86,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int // Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy)) { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name); } } return true; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs new file mode 100644 index 0000000000..3fbd869445 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs @@ -0,0 +1,190 @@ +using Unity.Netcode.Components; +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// NetworkTransform State Update Message + /// + internal struct NetworkTransformMessage : INetworkMessage + { + public int Version => 0; + private const string k_Name = "NetworkTransformMessage"; + + internal NetworkTransform NetworkTransform; + + // Only used for DAHost + internal NetworkTransform.NetworkTransformState State; + private FastBufferReader m_CurrentReader; + + private unsafe void CopyPayload(ref FastBufferWriter writer) + { + writer.WriteBytesSafe(m_CurrentReader.GetUnsafePtrAtCurrentPosition(), m_CurrentReader.Length - m_CurrentReader.Position); + } + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + if (m_CurrentReader.IsInitialized) + { + CopyPayload(ref writer); + } + else + { + NetworkTransform.SerializeMessage(writer, targetVersion); + } + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + var networkManager = context.SystemOwner as NetworkManager; + if (networkManager == null) + { + Debug.LogError($"[{nameof(NetworkTransformMessage)}] System owner context was not of type {nameof(NetworkManager)}!"); + return false; + } + var currentPosition = reader.Position; + var networkObjectId = (ulong)0; + var networkBehaviourId = 0; + + ByteUnpacker.ReadValueBitPacked(reader, out networkObjectId); + var isSpawnedLocally = networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId); + + // Only defer if the NetworkObject is not spawned yet and the local NetworkManager is not running as a DAHost. + if (!isSpawnedLocally && !networkManager.DAHost) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, networkObjectId, reader, ref context, k_Name); + return false; + } + + // While the below check and assignment might seem out of place, this is specific to running in DAHost mode when a NetworkObject is + // hidden from the DAHost but is visible to other clients. Since the DAHost needs to forward updates to the clients, we ignore processing + // this message locally + var networkObject = (NetworkObject)null; + var isServerAuthoritative = false; + var ownerAuthoritativeServerSide = false; + + // Get the behaviour index + ByteUnpacker.ReadValueBitPacked(reader, out networkBehaviourId); + + if (isSpawnedLocally) + { + networkObject = networkManager.SpawnManager.SpawnedObjects[networkObjectId]; + // Get the target NetworkTransform + NetworkTransform = networkObject.ChildNetworkBehaviours[networkBehaviourId] as NetworkTransform; + isServerAuthoritative = NetworkTransform.IsServerAuthoritative(); + ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer; + + reader.ReadNetworkSerializableInPlace(ref NetworkTransform.InboundState); + } + else + { + // Deserialize the state + reader.ReadNetworkSerializableInPlace(ref State); + } + + unsafe + { + if (ownerAuthoritativeServerSide) + { + var targetCount = 1; + + if (networkManager.DistributedAuthorityMode && networkManager.DAHost) + { + ByteUnpacker.ReadValueBitPacked(reader, out targetCount); + } + + var targetIds = stackalloc ulong[targetCount]; + + if (networkManager.DistributedAuthorityMode && networkManager.DAHost) + { + var targetId = (ulong)0; + for (int i = 0; i < targetCount; i++) + { + ByteUnpacker.ReadValueBitPacked(reader, out targetId); + targetIds[i] = targetId; + } + + if (!isSpawnedLocally) + { + // If we are the DAHost and the NetworkObject is hidden from the host we still need to forward this message + ownerAuthoritativeServerSide = networkManager.DAHost && !isSpawnedLocally; + } + } + + var ownerClientId = (ulong)0; + + if (networkObject != null) + { + ownerClientId = networkObject.OwnerClientId; + if (ownerClientId == NetworkManager.ServerClientId) + { + // Ownership must have changed, ignore any additional pending messages that might have + // come from a previous owner client. + return true; + } + } + else if (networkManager.DAHost) + { + // Specific to distributed authority mode, the only sender of state updates will be the owner + ownerClientId = context.SenderId; + } + + var networkDelivery = State.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; + + // Forward the state update if there are any remote clients to foward it to + if (networkManager.ConnectionManager.ConnectedClientsList.Count > (networkManager.IsHost ? 2 : 1)) + { + var clientCount = networkManager.DistributedAuthorityMode ? targetCount : networkManager.ConnectionManager.ConnectedClientsList.Count; + if (clientCount == 0) + { + return true; + } + + // This is only to copy the existing and already serialized struct for forwarding purposes only. + // This will not include any changes made to this struct at this particular stage of processing the message. + var currentMessage = this; + // Create a new reader that replicates this message + currentMessage.m_CurrentReader = new FastBufferReader(reader, Collections.Allocator.None); + // Rewind the new reader to the beginning of the message's payload + currentMessage.m_CurrentReader.Seek(currentPosition); + // Forward the message to all connected clients that are observers of the associated NetworkObject + + for (int i = 0; i < clientCount; i++) + { + var clientId = networkManager.DistributedAuthorityMode ? targetIds[i] : networkManager.ConnectionManager.ConnectedClientsList[i].ClientId; + if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) || + (!networkManager.DistributedAuthorityMode && !networkObject.Observers.Contains(clientId))) + { + continue; + } + + networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); + } + // Dispose of the reader used for forwarding + currentMessage.m_CurrentReader.Dispose(); + } + } + } + return true; + } + + public void Handle(ref NetworkContext context) + { + var networkManager = context.SystemOwner as NetworkManager; + // Only if the local NetworkManager instance is running as the DAHost we just exit if there is no local + // NetworkTransform component to apply the state update to (i.e. it is hidden from the DAHost and it + // just forwarded the state update to any other connected client) + if (networkManager.DAHost && NetworkTransform == null) + { + return; + } + + if (NetworkTransform == null) + { + Debug.LogError($"[{nameof(NetworkTransformMessage)}][Dropped] Reciever {nameof(NetworkTransform)} was not set!"); + return; + } + NetworkTransform.TransformStateUpdate(context.SenderId); + } + } +} diff --git a/com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Components/Messages/NetworkTransformMessage.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index ed6a46946c..4ca9c64eb6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -23,6 +23,8 @@ internal struct NetworkVariableDeltaMessage : INetworkMessage private FastBufferReader m_ReceivedNetworkVariableData; + private const string k_Name = "NetworkVariableDeltaMessage"; + // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. // Worth either merging or more cleanly separating these codepaths. public void Serialize(FastBufferWriter writer, int targetVersion) @@ -296,7 +298,7 @@ public void Handle(ref NetworkContext context) // DANGO-TODO: Fix me! // When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to // log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring. - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, GetType().Name); + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, k_Name); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index 473f775841..a55767d1d6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -6,6 +6,8 @@ internal struct ParentSyncMessage : INetworkMessage { public int Version => 0; + private const string k_Name = "DestroyObjectMessage"; + public ulong NetworkObjectId; private byte m_BitField; @@ -92,7 +94,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int // If the target NetworkObject does not exist =or= the target latest parent does not exist then defer the message if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId) || (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value))) { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name); + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name); return false; } return true; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index a15749e01e..2b1406cd95 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -99,6 +99,8 @@ internal struct ServerRpcMessage : INetworkMessage public FastBufferWriter WriteBuffer; public FastBufferReader ReadBuffer; + private const string k_Name = "ServerRpcMessage"; + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) { RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); @@ -106,7 +108,7 @@ public unsafe void Serialize(FastBufferWriter writer, int targetVersion) public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { - return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, k_Name); } public void Handle(ref NetworkContext context) @@ -134,6 +136,8 @@ internal struct ClientRpcMessage : INetworkMessage public FastBufferWriter WriteBuffer; public FastBufferReader ReadBuffer; + private const string k_Name = "ClientRpcMessage"; + public void Serialize(FastBufferWriter writer, int targetVersion) { RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); @@ -141,7 +145,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { - return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, k_Name); } public void Handle(ref NetworkContext context) @@ -169,6 +173,8 @@ internal struct RpcMessage : INetworkMessage public FastBufferWriter WriteBuffer; public FastBufferReader ReadBuffer; + private const string k_Name = "RpcMessage"; + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValuePacked(writer, SenderClientId); @@ -179,7 +185,7 @@ public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext conte { ByteUnpacker.ReadValuePacked(reader, out SenderClientId); - return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name); + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, k_Name); } public void Handle(ref NetworkContext context) diff --git a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef index 3b673da866..1577ff03f9 100644 --- a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef +++ b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef @@ -46,6 +46,21 @@ "name": "Unity", "expression": "2023", "define": "UNITY_DEDICATED_SERVER_ARGUMENTS_PRESENT" + }, + { + "name": "com.unity.modules.animation", + "expression": "", + "define": "COM_UNITY_MODULES_ANIMATION" + }, + { + "name": "com.unity.modules.physics", + "expression": "", + "define": "COM_UNITY_MODULES_PHYSICS" + }, + { + "name": "com.unity.modules.physics2d", + "expression": "", + "define": "COM_UNITY_MODULES_PHYSICS2D" } ] } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs index 75a1e9cb78..d6b02123a4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -475,10 +475,8 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) m_ReachedTarget = false; } - protected override void Update() + public override void OnUpdate() { - base.Update(); - if (CanCommitToTransform) { if (!m_ReachedTarget) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index ac64d2c1d1..1c7d98a84e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -63,9 +63,9 @@ public void StopMoving() private const int k_MaxThresholdFailures = 4; private int m_ExceededThresholdCount; - protected override void Update() + private void Update() { - base.Update(); + base.OnUpdate(); if (!IsSpawned || TestComplete) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs index 8effb7096c..e18bd2940d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs @@ -208,7 +208,7 @@ public IEnumerator ClientDisconnectMultipleClients() m_Clients[i].DisconnectLocalClient(); } - yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents); + yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5); // Check that we got the correct number of Disconnect events on the server. Assert.AreEqual(k_NumClients * 2, m_ServerEvents.Count); diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs index 051a3e3621..f6099c011d 100644 --- a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs @@ -45,7 +45,7 @@ public override void OnNetworkSpawn() } } - protected override void Update() + public override void OnUpdate() { if (!IsSpawned) { @@ -85,7 +85,7 @@ protected override void Update() } else { - base.Update(); + base.OnUpdate(); } } } From 0e1094850b332385c102e6acbc6775765541415e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 10 May 2024 12:56:47 -0500 Subject: [PATCH 048/236] chore: add second part of session restore (#2919) * update Skip initial in-scene object spawn pass and sending service the initial synchronization when restoring a session. Allow same scene handle entries when restoring a session. Don't spawn the player during connection approval during session restore. Integration test helpers registers validation handler to exclude InitTestScene for all clients. NetworkSceneManager always sends scene events to the service. For now, session owner always uses PostSynchronizationSceneUnloading when processing a session restore synchronization. --- .../Messages/ConnectionApprovedMessage.cs | 20 +++++++----- .../SceneManagement/NetworkSceneManager.cs | 31 +++++++++++++++++-- .../Runtime/NetcodeIntegrationTestHelpers.cs | 3 +- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 3c1b9cb4cc..89eee0cb39 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -236,16 +236,22 @@ public void Handle(ref NetworkContext context) // Mark the client being connected networkManager.IsConnectedClient = true; - // Spawn any in-scene placed NetworkObjects - networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + networkManager.SceneManager.IsRestoringSession = IsRestoredSession; - // Spawn the local player of the session owner - if (networkManager.AutoSpawnPlayerPrefabClientSide) + if (!IsRestoredSession) { - networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId); + // Spawn any in-scene placed NetworkObjects + networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + + // Spawn the local player of the session owner + if (networkManager.AutoSpawnPlayerPrefabClientSide) + { + networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId); + } + + // Synchronize the service with the initial session owner's loaded scenes and spawned objects + networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); } - // Synchronize the service with the initial session owner's loaded scenes and spawned objects - networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); } } ConnectedClientIds.Dispose(); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 02aab36c6e..2587ed5b6f 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -444,6 +444,7 @@ public List GetSynchronizedScenes() internal Dictionary ServerSceneHandleToClientSceneHandle = new Dictionary(); internal Dictionary ClientSceneHandleToServerSceneHandle = new Dictionary(); + internal bool IsRestoringSession; /// /// Add the client to server (and vice versa) scene handle lookup. /// Add the client-side handle to scene entry in the HandleToScene table. @@ -455,7 +456,7 @@ internal bool UpdateServerClientSceneHandle(int serverHandle, int clientHandle, { ServerSceneHandleToClientSceneHandle.Add(serverHandle, clientHandle); } - else + else if (!IsRestoringSession) { return false; } @@ -464,7 +465,7 @@ internal bool UpdateServerClientSceneHandle(int serverHandle, int clientHandle, { ClientSceneHandleToServerSceneHandle.Add(clientHandle, serverHandle); } - else + else if (!IsRestoringSession) { return false; } @@ -1045,7 +1046,7 @@ internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdH /// array of client identifiers to receive the scene event message private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds) { - if (targetClientIds.Length == 0) + if (targetClientIds.Length == 0 && !NetworkManager.DistributedAuthorityMode) { // This would be the Host/Server with no clients connected // Silently return as there is nothing to be done @@ -1056,6 +1057,16 @@ private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds) if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) { + if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority()) + { + sceneEvent.TargetClientId = NetworkManager.ServerClientId; + var message = new SceneEventMessage + { + EventData = sceneEvent, + }; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId); + NetworkManager.NetworkMetrics.TrackSceneEventSent(NetworkManager.ServerClientId, (uint)sceneEvent.SceneEventType, SceneNameFromHash(sceneEvent.SceneHash), size); + } foreach (var clientId in targetClientIds) { sceneEvent.TargetClientId = clientId; @@ -2430,6 +2441,12 @@ private void HandleClientSceneEvent(uint sceneEventId) networkObject.InternalNetworkSessionSynchronized(); } + if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority() && IsRestoringSession) + { + IsRestoringSession = false; + PostSynchronizationSceneUnloading = m_OriginalPostSynchronizationSceneUnloading; + } + EndSceneEvent(sceneEventId); } break; @@ -2616,6 +2633,8 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) /// internal bool SkipSceneHandling; + private bool m_OriginalPostSynchronizationSceneUnloading; + /// /// Both Client and Server: Incoming scene event entry point /// @@ -2689,6 +2708,12 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) // Only if ClientSynchronizationMode is Additive and the client receives a synchronize scene event if (ClientSynchronizationMode == LoadSceneMode.Additive) { + if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority() && IsRestoringSession && clientId == NetworkManager.ServerClientId) + { + m_OriginalPostSynchronizationSceneUnloading = PostSynchronizationSceneUnloading; + PostSynchronizationSceneUnloading = true; + } + // Check for scenes already loaded and create a table of scenes already loaded (SceneEntries) that will be // used if the server is synchronizing the same scenes (i.e. if a matching scene is already loaded on the // client side, then that scene will be used as opposed to loading another scene). This allows for clients diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index a29606688c..c952ade4a2 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -378,7 +378,8 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan { // If VerifySceneBeforeLoading is not already set, then go ahead and set it so the host/server // will not try to synchronize clients to the TestRunner scene. We only need to do this for the server. - if (networkManager.IsServer && networkManager.SceneManager.VerifySceneBeforeLoading == null) + // All clients in distributed authority mode, should have this registered (since any one client can become the session owner). + if ((networkManager.IsServer && networkManager.SceneManager.VerifySceneBeforeLoading == null) || networkManager.DistributedAuthorityMode) { networkManager.SceneManager.VerifySceneBeforeLoading = VerifySceneIsValidForClientsToLoad; From 4e35f84df9d9dcd28527ed4b60e6454b7073cc47 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 14 May 2024 08:30:29 -0500 Subject: [PATCH 049/236] fix: In-scene placed NetworkObjects getting destroyed if early disconnect (up-port) (#2924) * fix up-port of #2923 fix for in-scene placed NetworkObjects getting destroyed if a client disconnects early. * test adding test to validate fix * test fix Fixing issue of checking HasAuthority via NetworkObject as opposed to NetworkBehaviour --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- .../InSceneParentChildHandler.cs | 12 ++--- .../InScenePlacedNetworkObjectTests.cs | 47 +++++++++++++++++++ 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9cd30aaa16..305e08edc2 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 issue where in-scene placed NetworkObjects could be destroyed if a client disconnects early and/or before approval. (#2924) - Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2912) - Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2898) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index dcd7b8ba7a..e021505504 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1245,7 +1245,7 @@ internal void DespawnAndDestroyNetworkObjects() { // If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene // is unloaded. Otherwise, despawn and destroy it. - var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value); + var shouldDestroy = !(networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value)); // If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed if (shouldDestroy) diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs index 4164993e95..e11d1723f4 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs @@ -132,7 +132,7 @@ private bool CheckForAuthority() public void DeparentAllChildren(bool worldPositionStays = true) { - if (IsRootParent && CheckForAuthority()) + if (IsRootParent && HasAuthority) { var lastChild = GetLastChild(transform); if (lastChild != null) @@ -163,7 +163,7 @@ private void ParentChild(InSceneParentChildHandler child, bool worldPositionStay public void ReParentAllChildren(bool worldPositionStays = true) { - if (IsRootParent && CheckForAuthority()) + if (IsRootParent && HasAuthority) { ParentChild(m_Child, worldPositionStays); } @@ -171,7 +171,7 @@ public void ReParentAllChildren(bool worldPositionStays = true) public override void OnNetworkSpawn() { - if (CheckForAuthority()) + if (HasAuthority) { LogMessage($"[{NetworkObjectId}] Pos = ({m_TargetLocalPosition}) | Rotation ({m_TargetLocalRotation}) | Scale ({m_TargetLocalScale})"); if (AddNetworkTransform) @@ -206,7 +206,7 @@ public override void OnNetworkSpawn() public void DeparentSetValuesAndReparent() { - if (IsRootParent && CheckForAuthority()) + if (IsRootParent && HasAuthority) { // Back to back de-parenting and re-parenting s_GenerateRandomValues = true; @@ -222,7 +222,7 @@ public void DeparentSetValuesAndReparent() ///
public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { - if (!CheckForAuthority() || !IsSpawned || parentNetworkObject != null || !s_GenerateRandomValues) + if (!HasAuthority || !IsSpawned || parentNetworkObject != null || !s_GenerateRandomValues) { return; } @@ -242,7 +242,7 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj private void LateUpdate() { - if (!IsSpawned || !CheckForAuthority() || NetworkManagerTestDisabler.IsIntegrationTest) + if (!IsSpawned || !HasAuthority || NetworkManagerTestDisabler.IsIntegrationTest) { return; } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index 22b4e53c52..c2b77a4f42 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -578,4 +578,51 @@ protected bool ScaleValuesMatch(Transform transformA, Transform transformB) } } + + internal class InScenePlacedNetworkObjectClientTests : NetcodeIntegrationTest + { + private const string k_SceneToLoad = "InSceneNetworkObject"; + + protected override int NumberOfClients => 0; + + private Scene m_Scene; + + 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. + m_ServerNetworkManager.SpawnManager.DespawnAndDestroyNetworkObjects(); + + yield return s_DefaultWaitForTick; + + var insceneObject = GameObject.Find("InSceneObject"); + Assert.IsNotNull(insceneObject, $"Could not find the in-scene placed {nameof(NetworkObject)}: InSceneObject!"); + } + } } From 6a4bb8f68aa8b0055cb6a4e6d82ff33725aec1fb Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 21 May 2024 13:29:29 -0500 Subject: [PATCH 050/236] feat: enum ordered message versioning (#2929) * update Added the MessageTypeDefines ordering class that orders messages based on the NetworkMessageType. * test Fixing issue with tests using their own message provider. Fixing a few more tests that don't need the message ordering or count verification stuff to be running. Removed the message ordering related tests since this it was no longer needed. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Messaging/ILPPMessageProvider.cs | 111 +++++++++++++++++- .../Messaging/NetworkMessageManager.cs | 31 +---- .../Messaging/MessageRegistrationTests.cs | 72 ------------ .../NetworkManagerConfigurationTests.cs | 12 ++ .../Editor/Transports/UnityTransportTests.cs | 12 ++ 6 files changed, 137 insertions(+), 103 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 305e08edc2..3829a2f098 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed messages are now sorted by enum values as opposed to ordinally sorting the messages by their type name. (#2929) + ## [2.0.0-exp.2] - 2024-04-02 diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index 4424550e9a..609cadf3ec 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; @@ -12,9 +13,117 @@ internal struct ILPPMessageProvider : INetworkMessageProvider internal static readonly List __network_message_types = new List(); #pragma warning restore IDE1006 // restore naming rule violation check + /// + /// Enum representing the different types of messages that can be sent over the network. + /// The values cannot be changed, as they are used to serialize and deserialize messages. + /// Adding new messages should be done by adding new values to the end of the enum + /// using the next free value. + /// + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /// Add any new Message types to this table at the END with incremented index value + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + internal enum NetworkMessageTypes : uint + { + ConnectionApproved = 0, + ConnectionRequest = 1, + ChangeOwnership = 2, + ClientConnected = 3, + ClientDisconnected = 4, + ClientRpc = 5, + CreateObject = 6, + DestroyObject = 7, + DisconnectReason = 8, + ForwardClientRpc = 9, + ForwardServerRpc = 10, + NamedMessage = 11, + NetworkTransformMessage = 12, + NetworkVariableDelta = 13, + ParentSync = 14, + Proxy = 15, + Rpc = 16, + SceneEvent = 17, + ServerLog = 18, + ServerRpc = 19, + TimeSync = 20, + Unnamed = 21, + SessionOwner = 22 + } + + + // Enable this for integration tests that need no message types defined + internal static bool IntegrationTestNoMessages; + public List GetMessages() { - return __network_message_types; + // return no message types when defined for integration tests + if (IntegrationTestNoMessages) + { + return new List(); + } + var messageTypeCount = Enum.GetValues(typeof(NetworkMessageTypes)).Length; + // Assure the allowed types count is the same as our NetworkMessageType enum count + if (__network_message_types.Count != messageTypeCount) + { + throw new Exception($"Allowed types is not equal to the number of message type indices! Allowed Count: {__network_message_types.Count} | Index Count: {messageTypeCount}"); + } + + // Populate with blanks to be replaced later + var adjustedMessageTypes = new List(); + var blank = new NetworkMessageManager.MessageWithHandler(); + for (int i = 0; i < messageTypeCount; i++) + { + adjustedMessageTypes.Add(blank); + } + + // Create a type to enum index lookup table + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Add new Message types to this table paired with its new NetworkMessageTypes enum + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + var messageTypes = new Dictionary + { + { typeof(ConnectionApprovedMessage), NetworkMessageTypes.ConnectionApproved }, // This MUST be first + { typeof(ConnectionRequestMessage), NetworkMessageTypes.ConnectionRequest }, // This MUST be second + { typeof(ChangeOwnershipMessage), NetworkMessageTypes.ChangeOwnership }, + { typeof(ClientConnectedMessage), NetworkMessageTypes.ClientConnected }, + { typeof(ClientDisconnectedMessage), NetworkMessageTypes.ClientDisconnected }, + { typeof(ClientRpcMessage), NetworkMessageTypes.ClientRpc }, + { typeof(CreateObjectMessage), NetworkMessageTypes.CreateObject }, + { typeof(DestroyObjectMessage), NetworkMessageTypes.DestroyObject }, + { typeof(DisconnectReasonMessage), NetworkMessageTypes.DisconnectReason }, + { typeof(ForwardClientRpcMessage), NetworkMessageTypes.ForwardClientRpc }, + { typeof(ForwardServerRpcMessage), NetworkMessageTypes.ForwardServerRpc }, + { typeof(NamedMessage), NetworkMessageTypes.NamedMessage }, + { typeof(NetworkTransformMessage), NetworkMessageTypes.NetworkTransformMessage }, + { typeof(NetworkVariableDeltaMessage), NetworkMessageTypes.NetworkVariableDelta }, + { typeof(ParentSyncMessage), NetworkMessageTypes.ParentSync }, + { typeof(ProxyMessage), NetworkMessageTypes.Proxy }, + { typeof(RpcMessage), NetworkMessageTypes.Rpc }, + { typeof(SceneEventMessage), NetworkMessageTypes.SceneEvent }, + { typeof(ServerLogMessage), NetworkMessageTypes.ServerLog }, + { typeof(ServerRpcMessage), NetworkMessageTypes.ServerRpc }, + { typeof(TimeSyncMessage), NetworkMessageTypes.TimeSync }, + { typeof(UnnamedMessage), NetworkMessageTypes.Unnamed }, + { typeof(SessionOwnerMessage), NetworkMessageTypes.SessionOwner } + }; + + // Assure the type to lookup table count and NetworkMessageType enum count matches (i.e. to catch human error when adding new messages) + if (messageTypes.Count != messageTypeCount) + { + throw new Exception($"Message type to Message type index count mistmatch! Table Count: {messageTypes.Count} | Index Count: {messageTypeCount}"); + } + + // Now order the allowed types list based on the order of the NetworkMessageType enum + foreach (var messageHandler in __network_message_types) + { + if (!messageTypes.ContainsKey(messageHandler.MessageType)) + { + throw new Exception($"Missing message type from lookup table: {messageHandler.MessageType}"); + } + adjustedMessageTypes[(int)messageTypes[messageHandler.MessageType]] = messageHandler; + } + + // return the NetworkMessageType enum ordered list + return adjustedMessageTypes; } #if UNITY_EDITOR diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 7dd4dae2f0..b455c9bfa1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -120,49 +120,20 @@ internal struct MessageWithHandler public VersionGetter GetVersion; } - internal List PrioritizeMessageOrder(List allowedTypes) - { - var prioritizedTypes = new List(); - - // First pass puts the priority message in the first indices - // Those are the messages that must be delivered in order to allow re-ordering the others later - foreach (var t in allowedTypes) - { - if (t.MessageType.FullName == typeof(ConnectionRequestMessage).FullName || - t.MessageType.FullName == typeof(ConnectionApprovedMessage).FullName) - { - prioritizedTypes.Add(t); - } - } - - foreach (var t in allowedTypes) - { - if (t.MessageType.FullName != typeof(ConnectionRequestMessage).FullName && - t.MessageType.FullName != typeof(ConnectionApprovedMessage).FullName) - { - prioritizedTypes.Add(t); - } - } - - return prioritizedTypes; - } - public NetworkMessageManager(INetworkMessageSender sender, object owner, INetworkMessageProvider provider = null) { try { m_Sender = sender; m_Owner = owner; - if (provider == null) { provider = new ILPPMessageProvider(); } + // Get the presorted message types returned by the provider var allowedTypes = provider.GetMessages(); - allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName)); - allowedTypes = PrioritizeMessageOrder(allowedTypes); foreach (var type in allowedTypes) { RegisterMessageType(type); diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs index bb7ab651d7..2b601a16fd 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs @@ -191,77 +191,5 @@ public void WhenCreatingMessageSystem_BoundTypeMessageHandlersAreRegistered() Assert.AreEqual(handlerFour, systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]); } } - - internal class AAAEarlyLexicographicNetworkMessage : INetworkMessage - { - public void Serialize(FastBufferWriter writer, int targetVersion) - { - } - - public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) - { - return true; - } - - public void Handle(ref NetworkContext context) - { - } - - public int Version => 0; - } - -#pragma warning disable IDE1006 - internal class zzzLateLexicographicNetworkMessage : AAAEarlyLexicographicNetworkMessage - { - } -#pragma warning restore IDE1006 - - internal class OrderingMessageProvider : INetworkMessageProvider - { - public List GetMessages() - { - var listMessages = new List(); - - var messageWithHandler = new NetworkMessageManager.MessageWithHandler - { - MessageType = typeof(zzzLateLexicographicNetworkMessage), - GetVersion = NetworkMessageManager.CreateMessageAndGetVersion - }; - listMessages.Add(messageWithHandler); - - messageWithHandler.MessageType = typeof(ConnectionRequestMessage); - messageWithHandler.GetVersion = NetworkMessageManager.CreateMessageAndGetVersion; - listMessages.Add(messageWithHandler); - - messageWithHandler.MessageType = typeof(ConnectionApprovedMessage); - messageWithHandler.GetVersion = NetworkMessageManager.CreateMessageAndGetVersion; - listMessages.Add(messageWithHandler); - - messageWithHandler.MessageType = typeof(AAAEarlyLexicographicNetworkMessage); - messageWithHandler.GetVersion = NetworkMessageManager.CreateMessageAndGetVersion; - listMessages.Add(messageWithHandler); - - return listMessages; - } - } - - [Test] - public void MessagesGetPrioritizedCorrectly() - { - var sender = new NopMessageSender(); - var provider = new OrderingMessageProvider(); - using var messageManager = new NetworkMessageManager(sender, null, provider); - - // the 2 priority messages should appear first, in lexicographic order - Assert.AreEqual(messageManager.MessageTypes[0], typeof(ConnectionApprovedMessage)); - Assert.AreEqual(messageManager.MessageTypes[1], typeof(ConnectionRequestMessage)); - - // the other should follow after - Assert.AreEqual(messageManager.MessageTypes[2], typeof(AAAEarlyLexicographicNetworkMessage)); - Assert.AreEqual(messageManager.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage)); - - // there should not be any extras - Assert.AreEqual(messageManager.MessageHandlerCount, 4); - } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index 45ac91ce57..b57a13c974 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -11,6 +11,18 @@ namespace Unity.Netcode.EditorTests { public class NetworkManagerConfigurationTests { + [SetUp] + public void OnSetup() + { + ILPPMessageProvider.IntegrationTestNoMessages = true; + } + + [TearDown] + public void OnTearDown() + { + ILPPMessageProvider.IntegrationTestNoMessages = false; + } + /// /// Does a simple check to make sure the nested network manager will /// notify the user when in the editor. This is just a unit test to diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index fb61c053a1..f5caa6195e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -7,6 +7,18 @@ namespace Unity.Netcode.EditorTests { public class UnityTransportTests { + [SetUp] + public void OnSetup() + { + ILPPMessageProvider.IntegrationTestNoMessages = true; + } + + [TearDown] + public void OnTearDown() + { + ILPPMessageProvider.IntegrationTestNoMessages = false; + } + // Check that starting an IPv4 server succeeds. [Test] public void UnityTransport_BasicInitServer_IPv4() From 0060d33cf06ac5c9a8972f71795fae31669a14f8 Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Wed, 22 May 2024 16:40:23 -0400 Subject: [PATCH 051/236] chore: migrate tests from public to internal (#2930) * changing all test public api to internal * updating changelog * additional changes --------- Co-authored-by: Noel Stephens --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- .../Tests/Editor/ArithmeticTests.cs | 2 +- .../Tests/Editor/Build/BuildTests.cs | 2 +- .../Tests/Editor/DisconnectMessageTests.cs | 2 +- .../Tests/Editor/InterpolatorTests.cs | 2 +- .../Editor/Messaging/DisconnectOnSendTests.cs | 2 +- .../Messaging/MessageCorruptionTests.cs | 2 +- .../Editor/Messaging/MessageReceivingTests.cs | 2 +- .../Messaging/MessageRegistrationTests.cs | 2 +- .../Editor/Messaging/MessageSendingTests.cs | 2 +- .../Messaging/MessageVersioningTests.cs | 2 +- .../NetworkMetricsRegistrationTests.cs | 2 +- .../Tests/Editor/NetworkBehaviourTests.cs | 6 +-- .../NetworkManagerConfigurationTests.cs | 2 +- .../Tests/Editor/NetworkObjectTests.cs | 6 +-- .../Editor/NetworkPrefabProcessorTests.cs | 2 +- .../Editor/NetworkVar/NetworkVarTests.cs | 4 +- .../BaseFastBufferReaderWriterTest.cs | 2 +- .../Editor/Serialization/BitCounterTests.cs | 2 +- .../Editor/Serialization/BitReaderTests.cs | 2 +- .../Editor/Serialization/BitWriterTests.cs | 2 +- .../Serialization/BufferSerializerTests.cs | 2 +- .../Editor/Serialization/BytePackerTests.cs | 2 +- .../Serialization/FastBufferReaderTests.cs | 2 +- .../Serialization/FastBufferWriterTests.cs | 2 +- ...serBitReaderAndBitWriterTests_NCCBUG175.cs | 2 +- .../Timing/ClientNetworkTimeSystemTests.cs | 2 +- .../Tests/Editor/Timing/NetworkTimeTests.cs | 2 +- .../Timing/ServerNetworkTimeSystemTests.cs | 2 +- .../Tests/Editor/Timing/TimingTestHelper.cs | 2 +- .../Transports/BatchedReceiveQueueTests.cs | 2 +- .../Transports/BatchedSendQueueTests.cs | 2 +- .../Editor/Transports/UNetTransportTests.cs | 2 +- .../Editor/Transports/UnityTransportTests.cs | 2 +- .../Tests/Editor/XXHashTests.cs | 2 +- .../Tests/Runtime/AddNetworkPrefabTests.cs | 4 +- .../Tests/Runtime/ClientApprovalDenied.cs | 2 +- .../Runtime/ClientOnlyConnectionTests.cs | 2 +- .../BufferDataValidationComponent.cs | 2 +- .../NetworkVariableTestComponent.cs | 10 ++--- .../Components/NetworkVisibilityComponent.cs | 2 +- .../Tests/Runtime/ConnectionApproval.cs | 2 +- .../Runtime/ConnectionApprovalTimeoutTests.cs | 2 +- .../Tests/Runtime/DeferredMessagingTests.cs | 8 ++-- .../Tests/Runtime/DisconnectTests.cs | 2 +- .../DeferredDespawningTests.cs | 4 +- .../DistributeObjectsTests.cs | 6 +-- .../DistributedAuthorityCodecTests.cs | 4 +- .../NetworkClientAndPlayerObjectTests.cs | 6 +-- .../OwnershipPermissionsTests.cs | 4 +- .../Tests/Runtime/HiddenVariableTests.cs | 6 +-- .../Tests/Runtime/IntegrationTestExamples.cs | 12 +++--- .../Runtime/InvalidConnectionEventsTest.cs | 2 +- .../Tests/Runtime/ListChangedTest.cs | 6 +-- .../Messaging/DisconnectReasonTests.cs | 4 +- .../Runtime/Messaging/NamedMessageTests.cs | 2 +- .../Runtime/Messaging/UnnamedMessageTests.cs | 2 +- .../Runtime/Metrics/ConnectionMetricsTests.cs | 2 +- .../Runtime/Metrics/MessagingMetricsTests.cs | 2 +- .../Runtime/Metrics/MetricsDispatchTests.cs | 2 +- .../Runtime/Metrics/PacketLossMetricsTests.cs | 2 +- .../Runtime/NestedNetworkManagerTests.cs | 2 +- .../Runtime/NetworkBehaviourGenericTests.cs | 4 +- .../NetworkBehaviourPrePostSpawnTests.cs | 6 +-- .../Runtime/NetworkBehaviourUpdaterTests.cs | 6 +-- ...NetworkManagerCustomMessageManagerTests.cs | 2 +- .../Runtime/NetworkManagerEventsTests.cs | 2 +- .../NetworkManagerSceneManagerTests.cs | 2 +- .../Runtime/NetworkManagerTransportTests.cs | 4 +- .../NetworkObjectDestroyTests.cs | 4 +- .../NetworkObjectDontDestroyWithOwnerTests.cs | 2 +- ...orkObjectNetworkClientOwnedObjectsTests.cs | 2 +- .../NetworkObjectOnNetworkDespawnTests.cs | 2 +- .../NetworkObjectOnSpawnTests.cs | 6 +-- .../NetworkObjectOwnershipPropertiesTests.cs | 4 +- .../NetworkObjectOwnershipTests.cs | 4 +- .../NetworkObjectPropertyTests.cs | 2 +- .../NetworkObjectSpawnManyObjectsTests.cs | 4 +- .../NetworkObjectSynchronizationTests.cs | 12 +++--- .../Runtime/NetworkPrefabHandlerTests.cs | 4 +- .../Tests/Runtime/NetworkShowHideTests.cs | 4 +- .../Tests/Runtime/NetworkSpawnManagerTests.cs | 2 +- .../NetworkTransform/NetworkTransformBase.cs | 8 ++-- .../NetworkTransformGeneral.cs | 4 +- .../NetworkTransformOwnershipTests.cs | 6 +-- .../NetworkTransformPacketLossTests.cs | 4 +- .../NetworkTransformStateTests.cs | 2 +- .../NetworkTransform/NetworkTransformTests.cs | 4 +- .../Tests/Runtime/NetworkUpdateLoopTests.cs | 2 +- .../Tests/Runtime/NetworkVarBufferCopyTest.cs | 6 +-- .../Tests/Runtime/NetworkVariableTests.cs | 26 ++++++------ .../NetworkVariableTestsHelperTypes.cs | 40 +++++++++---------- ...tworkVariableUserSerializableTypesTests.cs | 16 ++++---- .../Tests/Runtime/NetworkVisibilityTests.cs | 2 +- .../Tests/Runtime/OwnerModifiedTests.cs | 4 +- .../Tests/Runtime/OwnerPermissionTests.cs | 4 +- .../Runtime/PeerDisconnectCallbackTests.cs | 2 +- .../Runtime/Physics/NetworkRigidbody2DTest.cs | 4 +- .../Runtime/Physics/NetworkRigidbodyTest.cs | 2 +- .../Tests/Runtime/PlayerObjectTests.cs | 2 +- .../Profiling/NetworkVariableNameTests.cs | 2 +- .../Tests/Runtime/RpcManyClientsTests.cs | 4 +- .../Tests/Runtime/RpcQueueTests.cs | 2 +- .../Tests/Runtime/RpcTests.cs | 12 +++--- .../Runtime/RpcTypeSerializationTests.cs | 4 +- .../NetworkBehaviourReferenceTests.cs | 4 +- .../NetworkObjectReferenceTests.cs | 6 +-- .../Tests/Runtime/StartStopTests.cs | 2 +- .../Tests/Runtime/StopStartRuntimeTests.cs | 2 +- .../Runtime/Timing/NetworkTimeSystemTests.cs | 6 +-- .../Runtime/Timing/TimeInitializationTest.cs | 2 +- .../Runtime/Timing/TimeIntegrationTest.cs | 2 +- .../Runtime/TransformInterpolationTests.cs | 4 +- .../UnityTransportConnectionTests.cs | 2 +- .../Transports/UnityTransportDriverClient.cs | 2 +- .../Transports/UnityTransportTestHelpers.cs | 6 +-- .../Runtime/Transports/UnityTransportTests.cs | 2 +- .../Tests/Runtime/UniversalRpcTests.cs | 4 +- 118 files changed, 234 insertions(+), 234 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3829a2f098..cb1b80e2f1 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,9 +22,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Change all the access modifiers of test class from Public to Internal (#2930) - Changed messages are now sorted by enum values as opposed to ordinally sorting the messages by their type name. (#2929) - ## [2.0.0-exp.2] - 2024-04-02 ### Added diff --git a/com.unity.netcode.gameobjects/Tests/Editor/ArithmeticTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/ArithmeticTests.cs index 45baf5a1c4..2791e4ac84 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/ArithmeticTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/ArithmeticTests.cs @@ -2,7 +2,7 @@ namespace Unity.Netcode.EditorTests { - public class ArithmeticTests + internal class ArithmeticTests { [Test] public void TestCeil() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs index bbb24a3d3b..563c43b166 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.EditorTests { - public class BuildTests + internal class BuildTests { public const string DefaultBuildScenePath = "Tests/Editor/Build/BuildTestScene.unity"; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs index 644ed70bbd..e30ceccb1f 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/DisconnectMessageTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.EditorTests { - public class DisconnectMessageTests + internal class DisconnectMessageTests { [Test] public void EmptyDisconnectReason() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index 224a25e0f8..4cdd06947e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.EditorTests { - public class InterpolatorTests + internal class InterpolatorTests { private const float k_Precision = 0.00000001f; private const int k_MockTickRate = 1; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/DisconnectOnSendTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/DisconnectOnSendTests.cs index 9fbf55abd3..a33e7ac6f4 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/DisconnectOnSendTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/DisconnectOnSendTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.EditorTests { - public class DisconnectOnSendTests + internal class DisconnectOnSendTests { private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageCorruptionTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageCorruptionTests.cs index 96544ac4a3..eb2ed8a18d 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageCorruptionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageCorruptionTests.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode.EditorTests { - public class MessageCorruptionTests + internal class MessageCorruptionTests { private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs index 1a9b3f644b..d77bf4ea72 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.EditorTests { - public class MessageReceivingTests + internal class MessageReceivingTests { private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs index 2b601a16fd..66898947e6 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageRegistrationTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.EditorTests { - public class MessageRegistrationTests + internal class MessageRegistrationTests { private struct TestMessageOne : INetworkMessage, INetworkSerializeByMemcpy { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs index 81cc7e7bb2..84967b7ee4 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode.EditorTests { - public class MessageSendingTests + internal class MessageSendingTests { private struct TestMessage : INetworkMessage, INetworkSerializeByMemcpy { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs index d19c882476..222c08b24b 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageVersioningTests.cs @@ -5,7 +5,7 @@ namespace Unity.Netcode.EditorTests { - public class MessageVersioningTests + internal class MessageVersioningTests { public static int SentVersion; public static int ReceivedVersion; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs index e962c86eb7..82b88ec019 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.EditorTests.Metrics { - public class NetworkMetricsRegistrationTests + internal class NetworkMetricsRegistrationTests { private static Type[] s_MetricTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(x => x.GetTypes()) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourTests.cs index 395aa79d95..29c827923d 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourTests.cs @@ -4,7 +4,7 @@ namespace Unity.Netcode.EditorTests { - public class NetworkBehaviourTests + internal class NetworkBehaviourTests { [Test] public void HasNetworkObjectTest() @@ -66,12 +66,12 @@ public void GivenClassDerivesFromNetworkBehaviourDerivedClass_GetTypeNameReturns // Note: in order to repro https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues/1078 // this child class must be defined before its parent to assure it is processed first by ILPP - public class DerivedNetworkBehaviour : EmptyNetworkBehaviour + internal class DerivedNetworkBehaviour : EmptyNetworkBehaviour { } - public class EmptyNetworkBehaviour : NetworkBehaviour + internal class EmptyNetworkBehaviour : NetworkBehaviour { } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs index b57a13c974..78b13e3abc 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.EditorTests { - public class NetworkManagerConfigurationTests + internal class NetworkManagerConfigurationTests { [SetUp] public void OnSetup() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs index dd0d930ea0..64dbc532be 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode.EditorTests { - public class NetworkObjectTests + internal class NetworkObjectTests { [Test] public void NetworkManagerOverrideTest() @@ -146,12 +146,12 @@ private struct ComponentIndices public int NetworkBehaviourIndex; } - public class EmptyNetworkBehaviour : NetworkBehaviour + internal class EmptyNetworkBehaviour : NetworkBehaviour { } - public class EmptyMonoBehaviour : MonoBehaviour + internal class EmptyMonoBehaviour : MonoBehaviour { } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkPrefabProcessorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkPrefabProcessorTests.cs index 275a83d70d..f50e502633 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkPrefabProcessorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkPrefabProcessorTests.cs @@ -5,7 +5,7 @@ namespace Unity.Netcode.EditorTests { - public class NetworkPrefabProcessorTests + internal class NetworkPrefabProcessorTests { private NetcodeForGameObjectsProjectSettings m_Settings; private bool m_EditorDefaultPrefabSetting; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs index e871b23315..77ad69dd62 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkVar/NetworkVarTests.cs @@ -3,9 +3,9 @@ namespace Unity.Netcode.EditorTests.NetworkVar { - public class NetworkVarTests + internal class NetworkVarTests { - public class NetworkVarComponent : NetworkBehaviour + internal class NetworkVarComponent : NetworkBehaviour { public NetworkVariable NetworkVariable = new NetworkVariable(); } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs index a73f151e66..53e72cfba1 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode.EditorTests { - public abstract class BaseFastBufferReaderWriterTest + internal abstract class BaseFastBufferReaderWriterTest { protected enum ByteEnum : byte { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs index 5e0f4c8a12..9200d6b987 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitCounterTests.cs @@ -2,7 +2,7 @@ namespace Unity.Netcode.EditorTests { - public class BitCounterTests + internal class BitCounterTests { [Test] public void WhenCountingUsedBitsIn64BitValue_ResultMatchesHighBitSetPlusOne([Range(0, 63)] int highBit) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs index b6e4ef5ff9..de83183880 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitReaderTests.cs @@ -4,7 +4,7 @@ namespace Unity.Netcode.EditorTests { - public class BitReaderTests + internal class BitReaderTests { [Test] public void TestReadingOneBit() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs index f35f725250..b4a84ff710 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BitWriterTests.cs @@ -4,7 +4,7 @@ namespace Unity.Netcode.EditorTests { - public class BitWriterTests + internal class BitWriterTests { [Test] public unsafe void TestWritingOneBit() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs index c36d738887..b6684ff0e0 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BufferSerializerTests.cs @@ -5,7 +5,7 @@ namespace Unity.Netcode.EditorTests { - public class BufferSerializerTests + internal class BufferSerializerTests { [Test] public void TestIsReaderIsWriter() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs index 99e8582fba..192f0db30d 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.EditorTests { - public class BytePackerTests + internal class BytePackerTests { private enum ByteEnum : byte { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs index 2b4c70ebcb..30b1ba895b 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.EditorTests { - public class FastBufferReaderTests : BaseFastBufferReaderWriterTest + internal class FastBufferReaderTests : BaseFastBufferReaderWriterTest { private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "") { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs index 5967d49488..98d423169e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.EditorTests { - public class FastBufferWriterTests : BaseFastBufferReaderWriterTest + internal class FastBufferWriterTests : BaseFastBufferReaderWriterTest { private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "") { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs index 5db3db1639..f279f4de05 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/UserBitReaderAndBitWriterTests_NCCBUG175.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.EditorTests { - public class UserBitReaderAndBitWriterTests_NCCBUG175 + internal class UserBitReaderAndBitWriterTests_NCCBUG175 { [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs index 8913ebc74a..275e891efa 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.EditorTests /// /// Tests for running a as a client. /// - public class ClientNetworkTimeSystemTests + internal class ClientNetworkTimeSystemTests { private const double k_AcceptableRttOffset = 0.03d; // 30ms offset is fine diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs index be53a0df7f..2a137bde2b 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/NetworkTimeTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.EditorTests { - public class NetworkTimeTests + internal class NetworkTimeTests { [Test] [TestCase(0d, 0u)] diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs index ea5fe00701..8d5edba0a4 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ServerNetworkTimeSystemTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.EditorTests { - public class ServerNetworkTimeSystemTests + internal class ServerNetworkTimeSystemTests { /// diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs index 16c840ded1..e1dee9b3a7 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/TimingTestHelper.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.EditorTests /// /// Helper functions for timing related tests. Allows to get a set of time steps and simulate time advancing without the need of a full playmode test. /// - public static class TimingTestHelper + internal static class TimingTestHelper { public static List GetRandomTimeSteps(float totalDuration, float min, float max, int seed) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs index e072f3c956..fef8a3b646 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.EditorTests { - public class BatchedReceiveQueueTests + internal class BatchedReceiveQueueTests { [Test] public void BatchedReceiveQueue_EmptyReader() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs index ca6a354b96..3ad4de1aa8 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.EditorTests { - public class BatchedSendQueueTests + internal class BatchedSendQueueTests { private const int k_TestQueueCapacity = 16 * 1024; private const int k_TestMessageSize = 1020; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UNetTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UNetTransportTests.cs index f8d7a46cd4..9cb0bda976 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UNetTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UNetTransportTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.EditorTests { - public class UNetTransportTests + internal class UNetTransportTests { [Test] public void StartServerReturnsFalseOnFailure() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index f5caa6195e..3bf49b64fb 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -5,7 +5,7 @@ namespace Unity.Netcode.EditorTests { - public class UnityTransportTests + internal class UnityTransportTests { [SetUp] public void OnSetup() diff --git a/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs index 342a3739d0..30fbb52aff 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/XXHashTests.cs @@ -2,7 +2,7 @@ namespace Unity.Netcode.EditorTests { - public class XXHashTests + internal class XXHashTests { [Test] public void TestXXHash32Short() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs index c00126e9ae..4627143c22 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs @@ -7,9 +7,9 @@ namespace Unity.Netcode.RuntimeTests { - public class AddNetworkPrefabTest : NetcodeIntegrationTest + internal class AddNetworkPrefabTest : NetcodeIntegrationTest { - public class EmptyComponent : NetworkBehaviour + internal class EmptyComponent : NetworkBehaviour { } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs index 5b4bc1e0ed..fd0517dd76 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ClientApprovalDenied.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { - public class ClientApprovalDenied : NetcodeIntegrationTest + internal class ClientApprovalDenied : NetcodeIntegrationTest { protected override int NumberOfClients => 2; private bool m_ApproveConnection = true; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs index 1e1be9eec3..dbd97fae53 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ClientOnlyConnectionTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { - public class ClientOnlyConnectionTests + internal class ClientOnlyConnectionTests { private NetworkManager m_ClientNetworkManager; private GameObject m_NetworkManagerGameObject; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs index 3912448423..3e46ec4b9e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests /// - Sending and Receiving a continually growing buffer up to (MaximumBufferSize) /// - Default maximum buffer size is 1MB /// - public class BufferDataValidationComponent : NetworkBehaviour + internal class BufferDataValidationComponent : NetworkBehaviour { /// /// Allows the external RPCQueueTest to begin testing or stop it diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs index 468600f8b3..346b908a17 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode.RuntimeTests { - public class EmbeddedManagedNetworkSerializableType : INetworkSerializable + internal class EmbeddedManagedNetworkSerializableType : INetworkSerializable { public int Int; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter @@ -14,7 +14,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade serializer.SerializeValue(ref Int); } } - public class ManagedNetworkSerializableType : INetworkSerializable, IEquatable + internal class ManagedNetworkSerializableType : INetworkSerializable, IEquatable { public string Str = ""; public int[] Ints = Array.Empty(); @@ -104,7 +104,7 @@ public override int GetHashCode() return 0; } } - public struct UnmanagedNetworkSerializableType : INetworkSerializable, IEquatable + internal struct UnmanagedNetworkSerializableType : INetworkSerializable, IEquatable { public FixedString32Bytes Str; public int Int; @@ -143,7 +143,7 @@ public override int GetHashCode() } - public struct UnmanagedTemplateNetworkSerializableType : INetworkSerializable where T : unmanaged, INetworkSerializable + internal struct UnmanagedTemplateNetworkSerializableType : INetworkSerializable where T : unmanaged, INetworkSerializable { public T Value; @@ -153,7 +153,7 @@ public void NetworkSerialize(BufferSerializer : INetworkSerializable where T : class, INetworkSerializable, new() + internal struct ManagedTemplateNetworkSerializableType : INetworkSerializable where T : class, INetworkSerializable, new() { public T Value; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVisibilityComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVisibilityComponent.cs index 2abf2c8243..856e77a817 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVisibilityComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVisibilityComponent.cs @@ -1,6 +1,6 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkVisibilityComponent : NetworkBehaviour + internal class NetworkVisibilityComponent : NetworkBehaviour { public void Hide() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs index bd14cfa161..ae606752d2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.RuntimeTests { - public class ConnectionApprovalTests + internal class ConnectionApprovalTests { private Guid m_ValidationToken; private bool m_IsValidated; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs index 0cf96580f6..d324bad7ef 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixture(ApprovalTimedOutTypes.ServerDoesNotRespond)] [TestFixture(ApprovalTimedOutTypes.ClientDoesNotRequest)] - public class ConnectionApprovalTimeoutTests : NetcodeIntegrationTest + internal class ConnectionApprovalTimeoutTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 564bde6fdb..cda0dd69e6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -116,7 +116,7 @@ public override void CleanupStaleTriggers() } } - public class DeferredMessageTestRpcComponent : NetworkBehaviour + internal class DeferredMessageTestRpcComponent : NetworkBehaviour { public bool ClientRpcCalled; @@ -137,7 +137,7 @@ public override void OnNetworkSpawn() } } - public class DeferredMessageTestNetworkVariableComponent : NetworkBehaviour + internal class DeferredMessageTestNetworkVariableComponent : NetworkBehaviour { public static readonly List ClientInstances = new List(); @@ -158,7 +158,7 @@ public override void OnNetworkSpawn() } } - public class DeferredMessageTestRpcAndNetworkVariableComponent : NetworkBehaviour + internal class DeferredMessageTestRpcAndNetworkVariableComponent : NetworkBehaviour { public static readonly List ClientInstances = new List(); public bool ClientRpcCalled; @@ -185,7 +185,7 @@ public void SendTestClientRpc() } } - public class DeferredMessagingTest : NetcodeIntegrationTest + internal class DeferredMessagingTest : NetcodeIntegrationTest { protected override int NumberOfClients => 0; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs index bdc298a6a6..c4997fe9fd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs @@ -21,7 +21,7 @@ namespace Unity.Netcode.RuntimeTests /// [TestFixture(OwnerPersistence.DestroyWithOwner)] [TestFixture(OwnerPersistence.DontDestroyWithOwner)] - public class DisconnectTests : NetcodeIntegrationTest + internal class DisconnectTests : NetcodeIntegrationTest { public enum OwnerPersistence { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs index b6abd74b0f..faba405079 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { - public class DeferredDespawningTests : IntegrationTestWithApproximation + internal class DeferredDespawningTests : IntegrationTestWithApproximation { private const int k_DaisyChainedCount = 5; protected override int NumberOfClients => 2; @@ -85,7 +85,7 @@ private void ReachedLastChainObject(ulong clientId) /// prefab driven by the authority. This repeats for the number specified in the integration /// test. /// - public class DeferredDespawnDaisyChained : NetworkBehaviour + internal class DeferredDespawnDaisyChained : NetworkBehaviour { public static bool EnableVerbose; public static Action ReachedLastChainInstance; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs index d6b02123a4..7957e6cf0c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -17,7 +17,7 @@ namespace Unity.Netcode.RuntimeTests /// Validates that distributable NetworkObjects are distributed upon /// a client connecting or disconnecting. ///
- public class DistributeObjectsTests : IntegrationTestWithApproximation + internal class DistributeObjectsTests : IntegrationTestWithApproximation { private GameObject m_DistributeObject; @@ -352,7 +352,7 @@ private void DisplayOwnership() /// This keeps track of each clients perspective of which NetworkObjects are owned by which client. /// It is used to validate that all clients are in synch with ownership updates. ///
- public class DistributeObjectsTestHelper : NetworkBehaviour + internal class DistributeObjectsTestHelper : NetworkBehaviour { /// /// [Client Context][Client Owners][NetworkObjectId][NetworkObject] @@ -426,7 +426,7 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) /// This is used to validate that upon distributed ownership changes NetworkTransform sycnhronization /// still works properly. /// - public class DistributeTestTransform : NetworkTransform + internal class DistributeTestTransform : NetworkTransform { private float m_DeltaVarPosition = 0.15f; private float m_DeltaVarQauternion = 0.015f; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 58dd33b9e9..39bbfce76f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -16,7 +16,7 @@ namespace Unity.Netcode.RuntimeTests { - public class DistributedAuthorityCodecTests : NetcodeIntegrationTest + internal class DistributedAuthorityCodecTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; @@ -35,7 +35,7 @@ public class DistributedAuthorityCodecTests : NetcodeIntegrationTest private GameObject m_SpawnObject; - public class TestNetworkComponent : NetworkBehaviour + internal class TestNetworkComponent : NetworkBehaviour { public NetworkList MyNetworkList = new NetworkList(new List { 1, 2, 3 }); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index fa7a641390..d7890c202f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -14,7 +14,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] [TestFixture(HostOrServer.DAHost)] - public class NetworkClientAndPlayerObjectTests : NetcodeIntegrationTest + internal class NetworkClientAndPlayerObjectTests : NetcodeIntegrationTest { private const int k_PlayerPrefabCount = 6; protected override int NumberOfClients => 2; @@ -168,7 +168,7 @@ private bool AllNetworkClientsValidated() [UnityTest] public IEnumerator ValidateNetworkClients() { - // Validate the initial clients created + // Validate the initial clients created yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated); AssertOnTimeout($"[Start] Not all NetworkClients were valid!\n{m_ErrorLogLevel1}"); @@ -190,7 +190,7 @@ public IEnumerator ValidateNetworkClients() } /// - /// Verify that all NetworkClients are pointing to the correct player object, even if + /// Verify that all NetworkClients are pointing to the correct player object, even if /// the player object is changed. /// private bool ValidatePlayerObjectOnClients(NetworkManager clientToValidate) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs index e56798bc86..beccbdd9db 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { - public class OwnershipPermissionsTests : IntegrationTestWithApproximation + internal class OwnershipPermissionsTests : IntegrationTestWithApproximation { private GameObject m_PermissionsObject; @@ -332,7 +332,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request reponse: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!"); } - public class OwnershipPermissionsTestHelper : NetworkBehaviour + internal class OwnershipPermissionsTestHelper : NetworkBehaviour { public static NetworkObject CurrentOwnedInstance; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs index c87fe80976..f110ed10aa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs @@ -7,11 +7,11 @@ namespace Unity.Netcode.RuntimeTests { - public class HiddenVariableTest : NetworkBehaviour + internal class HiddenVariableTest : NetworkBehaviour { } - public class HiddenVariableObject : NetworkBehaviour + internal class HiddenVariableObject : NetworkBehaviour { public static List ClientInstancesSpawned = new List(); @@ -69,7 +69,7 @@ public void ListChanged(NetworkListEvent listEvent) } } - public class HiddenVariableTests : NetcodeIntegrationTest + internal class HiddenVariableTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs b/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs index 7cba4fb9ad..0318e79433 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/IntegrationTestExamples.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { - public class IntegrationTestUpdated : NetcodeIntegrationTest + internal class IntegrationTestUpdated : NetcodeIntegrationTest { private GameObject m_MyNetworkPrefab; protected override int NumberOfClients => 1; @@ -45,7 +45,7 @@ public IEnumerator MyFirstIntegationTest() [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] - public class IntegrationTestExtended : NetcodeIntegrationTest + internal class IntegrationTestExtended : NetcodeIntegrationTest { private GameObject m_MyNetworkPrefab; protected override int NumberOfClients => 1; @@ -84,11 +84,11 @@ public IEnumerator MyFirstIntegationTest() } } - public class ExampleTestComponent : NetworkBehaviour + internal class ExampleTestComponent : NetworkBehaviour { } - public class IntegrationTestPlayers : NetcodeIntegrationTest + internal class IntegrationTestPlayers : NetcodeIntegrationTest { protected override int NumberOfClients => 5; @@ -126,13 +126,13 @@ public void TestClientRelativePlayers() } } - public class SpawnTest : NetworkBehaviour + internal class SpawnTest : NetworkBehaviour { public static int TotalSpawned; public override void OnNetworkSpawn() { TotalSpawned++; } public override void OnNetworkDespawn() { TotalSpawned--; } } - public class IntegrationTestSpawning : NetcodeIntegrationTest + internal class IntegrationTestSpawning : NetcodeIntegrationTest { protected override int NumberOfClients => 2; private GameObject m_NetworkPrefabToSpawn; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs index 11d3121c31..d849bc216a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode.RuntimeTests { - public class InvalidConnectionEventsTest : NetcodeIntegrationTest + internal class InvalidConnectionEventsTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs index 45f7081667..24a9111d40 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs @@ -6,12 +6,12 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkListChangedTestComponent : NetworkBehaviour + internal class NetworkListChangedTestComponent : NetworkBehaviour { } - public class ListChangedObject : NetworkBehaviour + internal class ListChangedObject : NetworkBehaviour { public int ExpectedPreviousValue = 0; public int ExpectedValue = 0; @@ -48,7 +48,7 @@ public void Changed(NetworkListEvent listEvent) [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] - public class NetworkListChangedTests : NetcodeIntegrationTest + internal class NetworkListChangedTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs index ce28966270..e293be3e64 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/DisconnectReasonTests.cs @@ -8,12 +8,12 @@ namespace Unity.Netcode.RuntimeTests { - public class DisconnectReasonObject : NetworkBehaviour + internal class DisconnectReasonObject : NetworkBehaviour { } - public class DisconnectReasonTests : NetcodeIntegrationTest + internal class DisconnectReasonTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs index ae243cc517..541fbb2fce 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NamedMessageTests : NetcodeIntegrationTest + internal class NamedMessageTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs index bd583cf5e2..61333ad003 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.RuntimeTests { - public class UnnamedMessageTests : NetcodeIntegrationTest + internal class UnnamedMessageTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs index 35356d4bcf..b94cc47725 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ConnectionMetricsTests.cs @@ -14,7 +14,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics [TestFixture(ClientCount.TwoClients, HostOrServer.Host)] [TestFixture(ClientCount.OneClient, HostOrServer.Server)] [TestFixture(ClientCount.TwoClients, HostOrServer.Server)] - public class ConnectionMetricsTests : NetcodeIntegrationTest + internal class ConnectionMetricsTests : NetcodeIntegrationTest { protected override int NumberOfClients => m_ClientCount; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs index 06b51c1d2f..6cd827f691 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics { - public class MessagingMetricsTests : DualClientMetricTestBase + internal class MessagingMetricsTests : DualClientMetricTestBase { private const uint k_MessageNameHashSize = 8; // Header is dynamically sized due to packing, will be 2 bytes for all test messages. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs index 519ddfa697..e8a268cf91 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics { - public class MetricsDispatchTests + internal class MetricsDispatchTests { private int m_NbDispatches; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index 4a008cfe5e..91c242b5fd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -15,7 +15,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics { - public class PacketLossMetricsTests : NetcodeIntegrationTest + internal class PacketLossMetricsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; private readonly int m_PacketLossRate = 25; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs index 7ae6ad2d4e..c31f8a83f5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NestedNetworkManagerTests.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NestedNetworkManagerTests + internal class NestedNetworkManagerTests { [Test] public void CheckNestedNetworkManager() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index ab48aaab09..91e3124932 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode.RuntimeTests /// /// This class is for testing general fixes or functionality of NetworkBehaviours /// - public class NetworkBehaviourGenericTests : NetcodeIntegrationTest + internal class NetworkBehaviourGenericTests : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -21,7 +21,7 @@ protected override bool CanStartServerAndClients() return m_AllowServerToStart; } - public class SimpleNetworkBehaviour : NetworkBehaviour + internal class SimpleNetworkBehaviour : NetworkBehaviour { public bool OnNetworkDespawnCalled; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs index ec5941aaa9..00d3c08613 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.DAHost)] - public class NetworkBehaviourPrePostSpawnTests : NetcodeIntegrationTest + internal class NetworkBehaviourPrePostSpawnTests : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -27,7 +27,7 @@ protected override void OnServerAndClientsCreated() base.OnServerAndClientsCreated(); } - public class NetworkBehaviourPreSpawn : NetworkBehaviour + internal class NetworkBehaviourPreSpawn : NetworkBehaviour { public static int ValueToSet; public bool OnNetworkPreSpawnCalled; @@ -55,7 +55,7 @@ public override void OnNetworkSpawn() } } - public class NetworkBehaviourPostSpawn : NetworkBehaviour + internal class NetworkBehaviourPostSpawn : NetworkBehaviour { public bool OnNetworkPostSpawnCalled; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs index 9cf021a5c7..6310b7caf4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode.RuntimeTests /// /// This is a refactor of the original test's NetworkBehaviour INetVarInfo derived NetworkBehaviours /// - public class NetVarContainer : NetworkBehaviour + internal class NetVarContainer : NetworkBehaviour { public enum NetVarsToCheck { @@ -126,7 +126,7 @@ public void SetNetworkVariableValues() /// Used to define how many NetworkVariables to use per NetVarContainer instance. /// There are always two ///
- public struct NetVarCombinationTypes + internal struct NetVarCombinationTypes { public NetVarContainer.NetVarsToCheck FirstType; public NetVarContainer.NetVarsToCheck SecondType; @@ -157,7 +157,7 @@ public struct NetVarCombinationTypes [TestFixture(HostOrServer.Host, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] [TestFixture(HostOrServer.Host, 2, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] [TestFixture(HostOrServer.Host, 2, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] - public class NetworkBehaviourUpdaterTests : NetcodeIntegrationTest + internal class NetworkBehaviourUpdaterTests : NetcodeIntegrationTest { // Go ahead and create maximum number of clients (not all tests will use them) protected override int NumberOfClients => m_NumberOfClients; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs index e3255b96d3..a43bd24746 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkManagerCustomMessageManagerTests + internal class NetworkManagerCustomMessageManagerTests { [Test] public void CustomMessageManagerAssigned() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index 3ae6828af9..1b872f91cc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkManagerEventsTests + internal class NetworkManagerEventsTests { private NetworkManager m_ClientManager; private NetworkManager m_ServerManager; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerSceneManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerSceneManagerTests.cs index dd9613268d..d7544b71f3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerSceneManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerSceneManagerTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkManagerSceneManagerTests + internal class NetworkManagerSceneManagerTests { [Test] public void SceneManagerAssigned() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs index fa1fbdaf3d..788758c90d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkManagerTransportTests + internal class NetworkManagerTransportTests { [Test] public void ClientDoesNotStartWhenTransportFails() @@ -106,7 +106,7 @@ public IEnumerator ShutsDownWhenTransportFails() /// /// Does nothing but simulate a transport that can fail at startup and/or when polling events. /// - public class FailedTransport : TestingNetworkTransport + internal class FailedTransport : TestingNetworkTransport { public bool FailOnStart = false; public bool FailOnNextPoll = false; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 7a751b3cd8..5ae711190e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -16,7 +16,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] - public class NetworkObjectDestroyTests : NetcodeIntegrationTest + internal class NetworkObjectDestroyTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -96,7 +96,7 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c } else { - // Shutdown the + // Shutdown the m_ClientNetworkManagers[0].Shutdown(); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs index f77a06e786..51e86ea496 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] - public class NetworkObjectDontDestroyWithOwnerTests : NetcodeIntegrationTest + internal class NetworkObjectDontDestroyWithOwnerTests : NetcodeIntegrationTest { private const int k_NumberObjectsToSpawn = 32; protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs index cd31ca218a..d9b0521e56 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkObjectNetworkClientOwnedObjectsTests : NetcodeIntegrationTest + internal class NetworkObjectNetworkClientOwnedObjectsTests : NetcodeIntegrationTest { private class DummyNetworkBehaviour : NetworkBehaviour { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs index 56adc0a686..9a737a4d3e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs @@ -13,7 +13,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] - public class NetworkObjectOnNetworkDespawnTests : NetcodeIntegrationTest + internal class NetworkObjectOnNetworkDespawnTests : NetcodeIntegrationTest { private const string k_ObjectName = "TestDespawn"; public enum InstanceTypes diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index cefdc2dcba..205ee38204 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] - public class NetworkObjectOnSpawnTests : NetcodeIntegrationTest + internal class NetworkObjectOnSpawnTests : NetcodeIntegrationTest { private GameObject m_TestNetworkObjectPrefab; private GameObject m_TestNetworkObjectInstance; @@ -43,7 +43,7 @@ private bool CheckClientsSideObserverTestObj() { if (m_ObserverTestType == ObserverTestTypes.WithObservers) { - // When validating this portion of the test and spawning with observers is true, there + // 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)) { @@ -52,7 +52,7 @@ private bool CheckClientsSideObserverTestObj() } else { - // When validating this portion of the test and spawning with observers is false, there + // 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)) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs index e198f0d118..70b918ce8d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixture(SessionModeTypes.DistributedAuthority)] [TestFixture(SessionModeTypes.ClientServer)] - public class NetworkObjectOwnershipPropertiesTests : NetcodeIntegrationTest + internal class NetworkObjectOwnershipPropertiesTests : NetcodeIntegrationTest { private class DummyNetworkBehaviour : NetworkBehaviour { @@ -186,7 +186,7 @@ public IEnumerator ValidatePropertiesWithOwnershipChanges([Values(InstanceTypes. yield return WaitForMessageReceived(m_ClientNetworkManagers.ToList()); } - // Ensure it's the ownership tables are updated + // Ensure it's the ownership tables are updated yield return WaitForConditionOrTimeOut(OwnershipPropagated); AssertOnTimeout($"Timed out waiting for ownership to propagate!\n{m_OwnershipPropagatedFailures}"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index 77d5a939a1..f1f6e53d53 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkObjectOwnershipComponent : NetworkBehaviour + internal class NetworkObjectOwnershipComponent : NetworkBehaviour { public static Dictionary SpawnedInstances = new Dictionary(); @@ -45,7 +45,7 @@ public void ResetFlags() [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] - public class NetworkObjectOwnershipTests : NetcodeIntegrationTest + internal class NetworkObjectOwnershipTests : NetcodeIntegrationTest { protected override int NumberOfClients => 9; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectPropertyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectPropertyTests.cs index 364a7cf05d..f1557f410e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectPropertyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectPropertyTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests /// /// Tests properties of NetworkObject for proper functionality. /// - public class NetworkObjectPropertyTests : NetcodeIntegrationTest + internal class NetworkObjectPropertyTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs index 7e415960bc..f684533ef2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(SessionModeTypes.ClientServer)] [TestFixture(SessionModeTypes.DistributedAuthority)] - public class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest + internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; // "many" in this case means enough to exceed a ushort_max message size written in the header @@ -20,7 +20,7 @@ public class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest public NetworkObjectSpawnManyObjectsTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } // Using this component assures we will know precisely how many prefabs were spawned on the client - public class SpawnObjecTrackingComponent : NetworkBehaviour + internal class SpawnObjecTrackingComponent : NetworkBehaviour { public static int SpawnedObjects; public override void OnNetworkSpawn() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index 0a907c6e16..c4714d7fe3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -14,7 +14,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(VariableLengthSafety.EnabledNetVarSafety, HostOrServer.Host)] [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Server)] [TestFixture(VariableLengthSafety.EnabledNetVarSafety, HostOrServer.Server)] - public class NetworkObjectSynchronizationTests : NetcodeIntegrationTest + internal class NetworkObjectSynchronizationTests : NetcodeIntegrationTest { private const int k_NumberToSpawn = 30; protected override int NumberOfClients => 0; @@ -382,7 +382,7 @@ public IEnumerator NetworkBehaviourOnSynchronize() /// the synchronization process will continue (i.e. it will skip over that block /// of the reader buffer). ///
- public class NetworkBehaviourWithNetworkVariables : NetworkBehaviour + internal class NetworkBehaviourWithNetworkVariables : NetworkBehaviour { public static int ServerSpawnCount { get; internal set; } public static readonly Dictionary ClientSpawnCount = new Dictionary(); @@ -442,7 +442,7 @@ public override void OnNetworkSpawn() /// when variable length safety checks are off NetworkVariables still are updated /// properly. ///
- public class NetworkBehaviourWithOwnerNetworkVariables : NetworkBehaviour + internal class NetworkBehaviourWithOwnerNetworkVariables : NetworkBehaviour { private NetworkVariableWritePermission m_NetworkVariableWritePermission = NetworkVariableWritePermission.Server; /// @@ -491,7 +491,7 @@ public override void OnNetworkSpawn() /// and provides a synchronization success version to validate that synchronization /// will continue if user synchronization code fails. /// - public class NetworkBehaviourSynchronizeFailureComponent : NetworkBehaviour + internal class NetworkBehaviourSynchronizeFailureComponent : NetworkBehaviour { public static int NumberOfFailureTypes { get; internal set; } public static int ServerSpawnCount { get; internal set; } @@ -661,11 +661,11 @@ protected override void OnSynchronize(ref BufferSerializer serializer) } } - public class NetworkBehaviourOnSynchronizeComponent : NetworkBehaviour + internal class NetworkBehaviourOnSynchronizeComponent : NetworkBehaviour { public SomeCustomSerializationData CustomSerializationData = new SomeCustomSerializationData(); - public struct SomeCustomSerializationData : INetworkSerializable + internal struct SomeCustomSerializationData : INetworkSerializable { public uint Value1; public bool Value2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs index 1e2378b7cf..2dc5f6001d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs @@ -14,7 +14,7 @@ namespace Unity.Netcode.RuntimeTests /// Destroying a newly spawned NetworkObject instance works /// Removing a INetworkPrefabInstanceHandler is removed and can be verified (very last check) ///
- public class NetworkPrefabHandlerTests + internal class NetworkPrefabHandlerTests { private const string k_TestPrefabObjectName = "NetworkPrefabTestObject"; @@ -203,7 +203,7 @@ public void TearDown() /// /// The Prefab instance handler to use for this test /// - public class NetworkPrefaInstanceHandler : INetworkPrefabInstanceHandler + internal class NetworkPrefaInstanceHandler : INetworkPrefabInstanceHandler { private NetworkObject m_NetworkObject; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 30325530c2..74ee054a31 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { - public class ShowHideObject : NetworkBehaviour + internal class ShowHideObject : NetworkBehaviour { public static List ClientTargetedNetworkObjects = new List(); public static ulong ClientIdToTarget; @@ -128,7 +128,7 @@ public void TriggerRpc() [TestFixture(SessionModeTypes.ClientServer)] [TestFixture(SessionModeTypes.DistributedAuthority)] - public class NetworkShowHideTests : NetcodeIntegrationTest + internal class NetworkShowHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs index 63a83dbd09..f5f5e36c3b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] - public class NetworkSpawnManagerTests : NetcodeIntegrationTest + internal class NetworkSpawnManagerTests : NetcodeIntegrationTest { private ulong serverSideClientId => NetworkManager.ServerClientId; private ulong clientSideClientId => m_ClientNetworkManagers[0].LocalClientId; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 9d725e0be8..da35c76fec 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkTransformBase : IntegrationTestWithApproximation + internal class NetworkTransformBase : IntegrationTestWithApproximation { // The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime @@ -731,7 +731,7 @@ private void PrintPositionRotationScaleDeltas() /// /// Helper component for all NetworkTransformTests /// - public class NetworkTransformTestComponent : NetworkTransform + internal class NetworkTransformTestComponent : NetworkTransform { public bool ServerAuthority; public bool ReadyToReceivePositionUpdate = false; @@ -794,7 +794,7 @@ public void CommitToTransform() /// Helper component for NetworkTransform parenting tests when /// a child is a parent of another child (i.e. "sub child") ///
- public class SubChildObjectComponent : ChildObjectComponent + internal class SubChildObjectComponent : ChildObjectComponent { protected override bool IsSubChild() { @@ -805,7 +805,7 @@ protected override bool IsSubChild() /// /// Helper component for NetworkTransform parenting tests /// - public class ChildObjectComponent : NetworkTransform + internal class ChildObjectComponent : NetworkTransform { public static int TestCount; public static bool EnableChildLog; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 17475b1713..3a7d0be8c7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixture(HostOrServer.Host, Authority.OwnerAuthority)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority)] - public class NetworkTransformGeneral : NetworkTransformBase + internal class NetworkTransformGeneral : NetworkTransformBase { public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority) : base(testWithHost, authority, RotationCompression.None, Rotation.Euler, Precision.Full) @@ -66,7 +66,7 @@ public void TestRotationThresholdDeltaCheck([Values] Interpolation interpolation var halfThreshold = m_AuthoritativeTransform.RotAngleThreshold * 0.5001f; // Apply the current state prior to getting reference rotations which assures we have - // applied the most current rotation deltas and that all bitset flags are updated + // applied the most current rotation deltas and that all bitset flags are updated var results = m_AuthoritativeTransform.ApplyState(); TimeTravelAdvanceTick(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index d6d35f397c..8f65f34694 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost, MotionModels.UseTransform)] [TestFixture(HostOrServer.DAHost, MotionModels.UseRigidbody)] [TestFixture(HostOrServer.Host, MotionModels.UseTransform)] - public class NetworkTransformOwnershipTests : IntegrationTestWithApproximation + internal class NetworkTransformOwnershipTests : IntegrationTestWithApproximation { public enum MotionModels { @@ -447,7 +447,7 @@ public IEnumerator ServerAuthoritativeTest() /// /// NetworkTransformOwnershipTests helper behaviour /// - public class VerifyObjectIsSpawnedOnClient : NetworkBehaviour + internal class VerifyObjectIsSpawnedOnClient : NetworkBehaviour { public static Dictionary NetworkManagerRelativeSpawnedObjects = new Dictionary(); @@ -512,7 +512,7 @@ public override void OnNetworkDespawn() /// This will have to be used to verify the ownership authority ///
[DisallowMultipleComponent] - public class TestClientNetworkTransform : NetworkTransform + internal class TestClientNetworkTransform : NetworkTransform { //public override void OnNetworkSpawn() //{ diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs index 69bf8b1e40..480905c68b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs @@ -26,7 +26,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] - public class NetworkTransformPacketLossTests : NetworkTransformBase + internal class NetworkTransformPacketLossTests : NetworkTransformBase { private const int k_Latency = 50; private const int k_PacketLoss = 2; @@ -490,7 +490,7 @@ private void OnAuthorityPushedTransformState(ref NetworkTransform.NetworkTransfo // Match the first position update if (Approximately(m_RandomPosition, networkTransformState.GetPosition())) { - // Teleport to the m_RandomPosition plus the + // Teleport to the m_RandomPosition plus the m_AuthoritativeTransform.SetState(m_TeleportOffset + m_RandomPosition, null, null, false); m_AuthoritativeTransform.AuthorityPushedTransformState -= OnAuthorityPushedTransformState; m_Teleported = true; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 6cbc832995..0b47dff72a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -15,7 +15,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(TransformSpace.World, Precision.Half, Rotation.Quaternion)] [TestFixture(TransformSpace.Local, Precision.Full, Rotation.Quaternion)] [TestFixture(TransformSpace.Local, Precision.Half, Rotation.Quaternion)] - public class NetworkTransformStateTests + internal class NetworkTransformStateTests { public enum SyncAxis { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index abaac55050..7b9ac9935d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -33,7 +33,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] #endif - public class NetworkTransformTests : NetworkTransformBase + internal class NetworkTransformTests : NetworkTransformBase { protected const int k_TickRate = 60; /// @@ -311,7 +311,7 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test Assert.True(WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated), $"[Non-Interpolate {i}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!"); // For 3 axis, we will skip validating that the non-authority interpolates to its target point at least once. - // This will validate that non-authoritative updates are maintaining their target state axis values if only 2 + // This will validate that non-authoritative updates are maintaining their target state axis values if only 2 // of the axis are being updated to assure interpolation maintains the targeted axial value per axis. // For 2 and 1 axis tests we always validate per delta update if (m_AxisExcluded || axisCount < 3) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs index 97f83c15fc..06d10d414f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkUpdateLoopTests + internal class NetworkUpdateLoopTests { [Test] public void RegisterCustomLoopInTheMiddle() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs index c7ee2afdf6..892e70d6fd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVarBufferCopyTest.cs @@ -8,9 +8,9 @@ namespace Unity.Netcode.RuntimeTests { [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] - public class NetworkVarBufferCopyTest : NetcodeIntegrationTest + internal class NetworkVarBufferCopyTest : NetcodeIntegrationTest { - public class DummyNetVar : NetworkVariableBase + internal class DummyNetVar : NetworkVariableBase { private const int k_DummyValue = 0x13579BDF; public bool DeltaWritten; @@ -75,7 +75,7 @@ public DummyNetVar( NetworkVariableWritePermission writePerm = DefaultWritePerm) : base(readPerm, writePerm) { } } - public class DummyNetBehaviour : NetworkBehaviour + internal class DummyNetBehaviour : NetworkBehaviour { public static bool DistributedAuthority; public DummyNetVar NetVar; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index bc625636a8..83b4633ce4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -13,7 +13,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixtureSource(nameof(TestDataSource))] - public class NetworkVariablePermissionTests : NetcodeIntegrationTest + internal class NetworkVariablePermissionTests : NetcodeIntegrationTest { public static IEnumerable TestDataSource() { @@ -304,7 +304,7 @@ public IEnumerator ServerCannotChangeOwnerWritableNetVar() } } - public struct TestStruct : INetworkSerializable, IEquatable + internal struct TestStruct : INetworkSerializable, IEquatable { public uint SomeInt; public bool SomeBool; @@ -344,7 +344,7 @@ public override int GetHashCode() } } - public class TestClass : INetworkSerializable, IEquatable + internal class TestClass : INetworkSerializable, IEquatable { public uint SomeInt; public bool SomeBool; @@ -386,12 +386,12 @@ public override int GetHashCode() // Used just to create a NetworkVariable in the templated NetworkBehaviour type that isn't referenced anywhere else // Please do not reference this class anywhere else! - public class TestClass_ReferencedOnlyByTemplateNetworkBehavourType : TestClass + internal class TestClass_ReferencedOnlyByTemplateNetworkBehavourType : TestClass { } - public class NetworkVariableTest : NetworkBehaviour + internal class NetworkVariableTest : NetworkBehaviour { public enum SomeEnum { @@ -440,7 +440,7 @@ public override void OnNetworkSpawn() [TestFixture(true)] #endif [TestFixture(false)] - public class NetworkVariableTests : NetcodeIntegrationTest + internal class NetworkVariableTests : NetcodeIntegrationTest { private const string k_StringTestValue = "abcdefghijklmnopqrstuvwxyz"; private static readonly FixedString32Bytes k_FixedStringTestValue = k_StringTestValue; @@ -5130,7 +5130,7 @@ protected override IEnumerator OnTearDown() /// that accepts anything derived from the class /// as a parameter. /// - public class NetworkListTestPredicate : ConditionalPredicateBase + internal class NetworkListTestPredicate : ConditionalPredicateBase { private const int k_MaxRandomValue = 1000; @@ -5296,7 +5296,7 @@ public NetworkListTestPredicate(NetworkVariableTest player1OnServer, NetworkVari } [TestFixtureSource(nameof(TestDataSource))] - public class NetworkVariableInheritanceTests : NetcodeIntegrationTest + internal class NetworkVariableInheritanceTests : NetcodeIntegrationTest { public NetworkVariableInheritanceTests(HostOrServer hostOrServer) : base(hostOrServer) @@ -5308,7 +5308,7 @@ public NetworkVariableInheritanceTests(HostOrServer hostOrServer) public static IEnumerable TestDataSource() => Enum.GetValues(typeof(HostOrServer)).OfType().Select(x => new TestFixtureData(x)); - public class ComponentA : NetworkBehaviour + internal class ComponentA : NetworkBehaviour { public NetworkVariable PublicFieldA = new NetworkVariable(1); protected NetworkVariable m_ProtectedFieldA = new NetworkVariable(2); @@ -5329,7 +5329,7 @@ public bool CompareValuesA(ComponentA other) } } - public class ComponentB : ComponentA + internal class ComponentB : ComponentA { public NetworkVariable PublicFieldB = new NetworkVariable(11); protected NetworkVariable m_ProtectedFieldB = new NetworkVariable(22); @@ -5350,7 +5350,7 @@ public bool CompareValuesB(ComponentB other) } } - public class ComponentC : ComponentB + internal class ComponentC : ComponentB { public NetworkVariable PublicFieldC = new NetworkVariable(111); protected NetworkVariable m_ProtectedFieldC = new NetworkVariable(222); @@ -5447,7 +5447,7 @@ public IEnumerator TestInheritedFields() } } - public class NetvarDespawnShutdown : NetworkBehaviour + internal class NetvarDespawnShutdown : NetworkBehaviour { private NetworkVariable m_IntNetworkVariable = new NetworkVariable(); private NetworkList m_IntList; @@ -5475,7 +5475,7 @@ public override void OnNetworkDespawn() /// Validates that setting values for NetworkVariable or NetworkList during the /// OnNetworkDespawn method will not cause an exception to occur. ///
- public class NetworkVariableModifyOnNetworkDespawn : NetcodeIntegrationTest + internal class NetworkVariableModifyOnNetworkDespawn : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs index 6f2752c03b..004de73550 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs @@ -5,24 +5,24 @@ namespace Unity.Netcode.RuntimeTests { - public class NetVarPermTestComp : NetworkBehaviour + internal class NetVarPermTestComp : NetworkBehaviour { public NetworkVariable OwnerWritable_Position = new NetworkVariable(Vector3.one, NetworkVariableBase.DefaultReadPerm, NetworkVariableWritePermission.Owner); public NetworkVariable ServerWritable_Position = new NetworkVariable(Vector3.one, NetworkVariableBase.DefaultReadPerm, NetworkVariableWritePermission.Server); public NetworkVariable OwnerReadWrite_Position = new NetworkVariable(Vector3.one, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner); } - public class NetworkVariableMiddleclass : NetworkVariable + internal class NetworkVariableMiddleclass : NetworkVariable { } - public class NetworkVariableSubclass : NetworkVariableMiddleclass + internal class NetworkVariableSubclass : NetworkVariableMiddleclass { } - public class NetworkBehaviourWithNetVarArray : NetworkBehaviour + internal class NetworkBehaviourWithNetVarArray : NetworkBehaviour { public NetworkVariable Int0 = new NetworkVariable(); public NetworkVariable Int1 = new NetworkVariable(); @@ -123,7 +123,7 @@ public void Foo() } } - public struct TemplatedValueOnlyReferencedByNetworkVariableSubclass : INetworkSerializeByMemcpy + internal struct TemplatedValueOnlyReferencedByNetworkVariableSubclass : INetworkSerializeByMemcpy where T : unmanaged { public T Value; @@ -178,7 +178,7 @@ public enum ULongEnum : ulong C = ulong.MaxValue } - public struct HashableNetworkVariableTestStruct : INetworkSerializeByMemcpy, IEquatable + internal struct HashableNetworkVariableTestStruct : INetworkSerializeByMemcpy, IEquatable { public byte A; public short B; @@ -220,7 +220,7 @@ public override int GetHashCode() } } - public struct HashMapKeyStruct : INetworkSerializeByMemcpy, IEquatable + internal struct HashMapKeyStruct : INetworkSerializeByMemcpy, IEquatable { public byte A; public short B; @@ -262,7 +262,7 @@ public override int GetHashCode() } } - public struct HashMapValStruct : INetworkSerializeByMemcpy, IEquatable + internal struct HashMapValStruct : INetworkSerializeByMemcpy, IEquatable { public byte A; public short B; @@ -304,7 +304,7 @@ public override int GetHashCode() } } - public struct NetworkVariableTestStruct : INetworkSerializeByMemcpy + internal struct NetworkVariableTestStruct : INetworkSerializeByMemcpy { public byte A; public short B; @@ -342,7 +342,7 @@ public static NetworkVariableTestStruct GetTestStruct() } - public class HashableNetworkVariableTestClass : INetworkSerializable, IEquatable + internal class HashableNetworkVariableTestClass : INetworkSerializable, IEquatable { public HashableNetworkVariableTestStruct Data; @@ -367,7 +367,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } - public class HashMapKeyClass : INetworkSerializable, IEquatable + internal class HashMapKeyClass : INetworkSerializable, IEquatable { public HashMapKeyStruct Data; @@ -392,7 +392,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } - public class HashMapValClass : INetworkSerializable, IEquatable + internal class HashMapValClass : INetworkSerializable, IEquatable { public HashMapValStruct Data; @@ -417,7 +417,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } - public class NetworkVariableTestClass : INetworkSerializable, IEquatable + internal class NetworkVariableTestClass : INetworkSerializable, IEquatable { public NetworkVariableTestStruct Data; @@ -446,7 +446,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade // The ILPP code for NetworkVariables to determine how to serialize them relies on them existing as fields of a NetworkBehaviour to find them. // Some of the tests below create NetworkVariables on the stack, so this class is here just to make sure the relevant types are all accounted for. - public class NetVarILPPClassForTests : NetworkBehaviour + internal class NetVarILPPClassForTests : NetworkBehaviour { public NetworkVariable ByteVar; public NetworkVariable> ByteArrayVar; @@ -887,34 +887,34 @@ public class NetVarILPPClassForTests : NetworkBehaviour public NetworkVariableSubclass> SubclassVar; } - public class TemplateNetworkBehaviourType : NetworkBehaviour + internal class TemplateNetworkBehaviourType : NetworkBehaviour { public NetworkVariable TheVar; } - public class IntermediateNetworkBehavior : TemplateNetworkBehaviourType + internal class IntermediateNetworkBehavior : TemplateNetworkBehaviourType { public NetworkVariable TheVar2; } #if !NGO_MINIMALPROJECT - public class ClassHavingNetworkBehaviour : IntermediateNetworkBehavior + internal class ClassHavingNetworkBehaviour : IntermediateNetworkBehavior { } // Please do not reference TestClass_ReferencedOnlyByTemplateNetworkBehavourType anywhere other than here! - public class ClassHavingNetworkBehaviour2 : TemplateNetworkBehaviourType + internal class ClassHavingNetworkBehaviour2 : TemplateNetworkBehaviourType { } - public class StructHavingNetworkBehaviour : TemplateNetworkBehaviourType + internal class StructHavingNetworkBehaviour : TemplateNetworkBehaviourType { } #endif - public struct StructUsedOnlyInNetworkList : IEquatable, INetworkSerializeByMemcpy + internal struct StructUsedOnlyInNetworkList : IEquatable, INetworkSerializeByMemcpy { public int Value; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs index 4cc8811202..3c61d9258b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs @@ -9,15 +9,15 @@ namespace Unity.Netcode.RuntimeTests { - public struct MyTypeOne + internal struct MyTypeOne { public int Value; } - public struct MyTypeTwo + internal struct MyTypeTwo { public int Value; } - public struct MyTypeThree + internal struct MyTypeThree { public int Value; } @@ -25,7 +25,7 @@ public struct MyTypeThree /// /// Used to help track instances of any child derived class /// - public class WorkingUserNetworkVariableComponentBase : NetworkBehaviour + internal class WorkingUserNetworkVariableComponentBase : NetworkBehaviour { private static Dictionary s_Instances = new Dictionary(); @@ -68,16 +68,16 @@ public override void OnNetworkDespawn() } } - public class WorkingUserNetworkVariableComponent : WorkingUserNetworkVariableComponentBase + internal class WorkingUserNetworkVariableComponent : WorkingUserNetworkVariableComponentBase { public NetworkVariable NetworkVariable = new NetworkVariable(); } - public class WorkingUserNetworkVariableComponentUsingExtensionMethod : WorkingUserNetworkVariableComponentBase + internal class WorkingUserNetworkVariableComponentUsingExtensionMethod : WorkingUserNetworkVariableComponentBase { public NetworkVariable NetworkVariable = new NetworkVariable(); } - public class NonWorkingUserNetworkVariableComponent : NetworkBehaviour + internal class NonWorkingUserNetworkVariableComponent : NetworkBehaviour { public NetworkVariable NetworkVariable = new NetworkVariable(); } @@ -96,7 +96,7 @@ public static void ReadValueSafe(this FastBufferReader reader, out MyTypeTwo val } } - public class NetworkVariableUserSerializableTypesTests : NetcodeIntegrationTest + internal class NetworkVariableUserSerializableTypesTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs index 037481c550..7d337110e4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.DistributedAuthority)] [TestFixture(SceneManagementState.SceneManagementEnabled, SessionModeTypes.ClientServer)] [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.ClientServer)] - public class NetworkVisibilityTests : NetcodeIntegrationTest + internal class NetworkVisibilityTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs index 6de84b1be1..3a925b9332 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerModifiedTests.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode.RuntimeTests // Where the NetworkVariable updates would be repeated on some clients. // The twist comes fom the updates needing to happens very specifically for the issue to repro in tests - public class OwnerModifiedObject : NetworkBehaviour, INetworkUpdateSystem + internal class OwnerModifiedObject : NetworkBehaviour, INetworkUpdateSystem { public NetworkList MyNetworkList; @@ -76,7 +76,7 @@ public void InitializeLastCient() [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] - public class OwnerModifiedTests : NetcodeIntegrationTest + internal class OwnerModifiedTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs index 5f8501d889..8ff4098fcb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/OwnerPermissionTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { - public class OwnerPermissionObject : NetworkBehaviour + internal class OwnerPermissionObject : NetworkBehaviour { // indexed by [object, machine] public static OwnerPermissionObject[,] Objects = new OwnerPermissionObject[3, 3]; @@ -85,7 +85,7 @@ public void ListServerChanged(NetworkListEvent listEvent) } } - public class OwnerPermissionHideTests : NetcodeIntegrationTest + internal class OwnerPermissionHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index f78dbf678b..896aa90bf5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -21,7 +21,7 @@ namespace Unity.Netcode.RuntimeTests ///
[TestFixture(HostOrServer.Server)] [TestFixture(HostOrServer.Host)] - public class PeerDisconnectCallbackTests : NetcodeIntegrationTest + internal class PeerDisconnectCallbackTests : NetcodeIntegrationTest { public enum ClientDisconnectType diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs index f92cab975c..089eeee542 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs @@ -8,12 +8,12 @@ namespace Unity.Netcode.RuntimeTests { - public class NetworkRigidbody2DDynamicTest : NetworkRigidbody2DTestBase + internal class NetworkRigidbody2DDynamicTest : NetworkRigidbody2DTestBase { public override bool Kinematic => false; } - public class NetworkRigidbody2DKinematicTest : NetworkRigidbody2DTestBase + internal class NetworkRigidbody2DKinematicTest : NetworkRigidbody2DTestBase { public override bool Kinematic => true; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index 0a68e0d069..9ec32bc57f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -13,7 +13,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(RigidbodyInterpolation.Extrapolate, false, true)] // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation [TestFixture(RigidbodyInterpolation.Interpolate, true, false)] // This should not allow kinematic instances to have Rigidbody interpolation enabled [TestFixture(RigidbodyInterpolation.Interpolate, false, false)] // Testing that rigid body interpolation remains the same if NT interpolate is disabled - public class NetworkRigidbodyTest : NetcodeIntegrationTest + internal class NetworkRigidbodyTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; private bool m_NetworkTransformInterpolate; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs index 2543c4d524..8fba758de9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] - public class PlayerObjectTests : NetcodeIntegrationTest + internal class PlayerObjectTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs index 25d2e3bc14..1ec833369c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs @@ -4,7 +4,7 @@ namespace Unity.Netcode.RuntimeTests { - public sealed class NetworkVariableNameTests + internal sealed class NetworkVariableNameTests { private NetworkVariableNameComponent m_NetworkVariableNameComponent; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs index 40de8a185a..5af6daa6c0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode.RuntimeTests { - public class RpcManyClientsObject : NetworkBehaviour + internal class RpcManyClientsObject : NetworkBehaviour { public int Count = 0; public List ReceivedFrom = new List(); @@ -43,7 +43,7 @@ public void WithParamsClientRpc(ClientRpcParams param) } } - public class RpcManyClientsTests : NetcodeIntegrationTest + internal class RpcManyClientsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 10; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs index dfa5f5ec9a..5d7775fa95 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs @@ -13,7 +13,7 @@ namespace Unity.Netcode.RuntimeTests /// - That all RPCs invoke at the appropriate `NetworkUpdateStage` (Client and Server) /// - A lower level `MessageQueueContainer` test that validates `MessageQueueFrameItems` after they have been put into the queue ///
- public class RpcQueueTests + internal class RpcQueueTests { [SetUp] public void Setup() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs index ecb9337615..23c546e4fa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs @@ -9,14 +9,14 @@ namespace Unity.Netcode.RuntimeTests { - public class RpcTests : NetcodeIntegrationTest + internal class RpcTests : NetcodeIntegrationTest { - public class CompileTimeNoRpcsBaseClassTest : NetworkBehaviour + internal class CompileTimeNoRpcsBaseClassTest : NetworkBehaviour { } - public class CompileTimeHasRpcsChildClassDerivedFromNoRpcsBaseClassTest : CompileTimeNoRpcsBaseClassTest + internal class CompileTimeHasRpcsChildClassDerivedFromNoRpcsBaseClassTest : CompileTimeNoRpcsBaseClassTest { [ServerRpc] public void SomeDummyServerRpc() @@ -25,7 +25,7 @@ public void SomeDummyServerRpc() } } - public class GenericRpcTestNB : NetworkBehaviour where T : unmanaged + internal class GenericRpcTestNB : NetworkBehaviour where T : unmanaged { public event Action OnServer_Rpc; @@ -36,11 +36,11 @@ public void MyServerRpc(T clientId, ServerRpcParams param = default) } } - public class RpcTestNBFloat : GenericRpcTestNB + internal class RpcTestNBFloat : GenericRpcTestNB { } - public class RpcTestNB : GenericRpcTestNB + internal class RpcTestNB : GenericRpcTestNB { #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT public event Action, ServerRpcParams> OnNativeListServer_Rpc; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs index 3d57e4d4ef..8da15ccbe8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs @@ -14,14 +14,14 @@ namespace Unity.Netcode.RuntimeTests { - public class RpcTypeSerializationTests : NetcodeIntegrationTest + internal class RpcTypeSerializationTests : NetcodeIntegrationTest { public RpcTypeSerializationTests() { m_UseHost = false; } - public class RpcTestNB : NetworkBehaviour + internal class RpcTestNB : NetworkBehaviour { public delegate void OnReceivedDelegate(object obj); public OnReceivedDelegate OnReceived; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index 472d6176ce..c3f63b941f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -13,7 +13,7 @@ namespace Unity.Netcode.RuntimeTests /// - Deserializing NetworkObjectReference to NetworkObject /// - Implicit operators of NetworkObjectReference ///
- public class NetworkBehaviourReferenceTests : IDisposable + internal class NetworkBehaviourReferenceTests : IDisposable { private class TestNetworkBehaviour : NetworkBehaviour { @@ -156,7 +156,7 @@ public NetworkBehaviourReferenceTests() /// /// Integration tests for NetworkBehaviourReference /// - public class NetworkBehaviourReferenceIntegrationTests : NetcodeIntegrationTest + internal class NetworkBehaviourReferenceIntegrationTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 8b29d17e62..084e4d50dc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -15,7 +15,7 @@ namespace Unity.Netcode.RuntimeTests /// - Deserializing NetworkObjectReference to NetworkObject /// - Implicit operators of NetworkObjectReference ///
- public class NetworkObjectReferenceTests : IDisposable + internal class NetworkObjectReferenceTests : IDisposable { private class TestNetworkBehaviour : NetworkBehaviour { @@ -340,7 +340,7 @@ public NetworkObjectReferenceTests() /// Helper method for tests to create and destroy Unity Objects. ///
/// The type of Object this context incorporates. - public class UnityObjectContext : UnityObjectContext where T : Object + internal class UnityObjectContext : UnityObjectContext where T : Object { private T m_Object; @@ -353,7 +353,7 @@ internal UnityObjectContext(T unityObject, Object root) public T Object => m_Object; } - public class UnityObjectContext : IDisposable + internal class UnityObjectContext : IDisposable { private Object m_Root; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/StartStopTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/StartStopTests.cs index 403404d33c..bb928e1746 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/StartStopTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/StartStopTests.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.RuntimeTests { - public class StartStopTests + internal class StartStopTests { private NetworkManager m_NetworkManager; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs index 8d52f1fc63..8ec1c37473 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/StopStartRuntimeTests.cs @@ -5,7 +5,7 @@ namespace Unity.Netcode.RuntimeTests { - public class StopStartRuntimeTests : NetcodeIntegrationTest + internal class StopStartRuntimeTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs index 047435fa26..c38bc86275 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode.RuntimeTests /// /// Runtime tests to test the network time system with the Unity player loop. /// - public class NetworkTimeSystemTests + internal class NetworkTimeSystemTests { private MonoBehaviourTest m_PlayerLoopFixedTimeTestComponent; // cache for teardown private MonoBehaviourTest m_PlayerLoopTimeTestComponent; // cache for teardown @@ -117,7 +117,7 @@ public void TearDown() } } - public class PlayerLoopFixedTimeTestComponent : MonoBehaviour, IMonoBehaviourTest + internal class PlayerLoopFixedTimeTestComponent : MonoBehaviour, IMonoBehaviourTest { public const int Passes = 100; @@ -184,7 +184,7 @@ private void FixedUpdate() public bool IsTestFinished => m_UpdatePasses >= Passes; } - public class PlayerLoopTimeTestComponent : MonoBehaviour, IMonoBehaviourTest + internal class PlayerLoopTimeTestComponent : MonoBehaviour, IMonoBehaviourTest { public const int Passes = 100; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs index fe7d935f6c..5d8f39c444 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests /// /// Tests that the time and tick system are initialized properly /// - public class TimeInitializationTest + internal class TimeInitializationTest { private int m_ClientTickCounter; private int m_ConnectedTick; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs index e019bb2137..f6f8147dc4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeIntegrationTest.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests /// /// Tests the times of two clients connecting to a server using the SIPTransport (returns 50ms RTT but has no latency simulation) /// - public class TimeIntegrationTest : NetcodeIntegrationTest + internal class TimeIntegrationTest : NetcodeIntegrationTest { private const double k_AdditionalTimeTolerance = 0.3333d; // magic number and in theory not needed but without this mac os test fail in Yamato because it looks like we get random framerate drops during unit test. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 1c7d98a84e..9c402ef60e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.RuntimeTests { - public class TransformInterpolationObject : NetworkTransform + internal class TransformInterpolationObject : NetworkTransform { public static bool TestComplete = false; // Set the minimum threshold which we will use as our margin of error @@ -140,7 +140,7 @@ private void Update() } } - public class TransformInterpolationTests : NetcodeIntegrationTest + internal class TransformInterpolationTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs index e18bd2940d..ac4c0891c2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode.RuntimeTests { - public class UnityTransportConnectionTests + internal class UnityTransportConnectionTests { // For tests using multiple clients. private const int k_NumClients = 5; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs index e2d6ef16aa..ef8ca268e4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs @@ -15,7 +15,7 @@ namespace Unity.Netcode.RuntimeTests // The only reason it's defined as a MonoBehaviour is that OnDestroy is the only reliable way // to get the driver's Dispose method called from a UnityTest. Making it disposable would be // the preferred solution, but that doesn't always mesh well with coroutines. - public class UnityTransportDriverClient : MonoBehaviour + internal class UnityTransportDriverClient : MonoBehaviour { private NetworkDriver m_Driver; public NetworkDriver Driver => m_Driver; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs index 75144f66c3..62ae2b561f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.RuntimeTests { - public static class UnityTransportTestHelpers + internal static class UnityTransportTestHelpers { // Half a second might seem like a very long time to wait for a network event, but in CI // many of the machines are underpowered (e.g. old Android devices or Macs) and there are @@ -59,7 +59,7 @@ public static void InitializeTransport(out UnityTransport transport, out List m_Events = new List(); public List Events => m_Events; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index d2dcddf7ea..4a14206e02 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode.RuntimeTests { - public class UnityTransportTests + internal class UnityTransportTests { // No need to test all reliable delivery methods since they all map to the same pipeline. private static readonly NetworkDelivery[] k_DeliveryParameters = diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs index a756bd7996..3aeb06d05d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -19,7 +19,7 @@ // I put them in their own namespace so they would be easier to navigate in the test list. namespace Unity.Netcode.RuntimeTests.UniversalRpcTests { - public class UniversalRpcNetworkBehaviour : NetworkBehaviour + internal class UniversalRpcNetworkBehaviour : NetworkBehaviour { public bool Stop = false; public string Received = string.Empty; @@ -534,7 +534,7 @@ public void SelfRecursiveRpc() } } - public class UniversalRpcTestsBase : NetcodeIntegrationTest + internal class UniversalRpcTestsBase : NetcodeIntegrationTest { protected override int NumberOfClients => 2; From 3f59dff4e452ed838c81fde713b54be602c3e1ba Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Fri, 31 May 2024 17:24:37 -0400 Subject: [PATCH 052/236] chore: NGO 2.0.0-exp.4 merge back to develop NGO 2.0.0 (#2940) * chore: distributed authority UI displays dependent upon multiplayer SDK package (#2875) * update Renaming Session Mode to Network Topology. This includes any related properties or XML API documentation references. Adding package dependent conditional define MULTIPLAYER_SDK_INSTALLED that is defined when the com.unity.services.multiplayer package is installed to the editor assembly definition file. This removes the custom property drawer since it appears the default enum flag drawer visual bug is resolved in Unity 6. incrementing the version change * feat: expose the UTP connect (#2935) * feat: expose the UTP connect payload * pass accept payload * fix formatting * make Connect and m_Driver protected * remove payload output * chore: Merge develop 2.0.0 into exp.3 (#2933) * feat: enum ordered message versioning (#2929) * chore: migrate tests from public to internal (#2930) * update Added the MessageTypeDefines ordering class that orders messages based on the NetworkMessageType. Use tick offset. Change the ticks ago based on whether DAHost or CMBService. Changing all test public api to internal Adding FixedJoint attach and detach methods to NetworkRigidbodyBase. * test Fixing issue with tests using their own message provider. Fixing a few more tests that don't need the message ordering or count verification stuff to be running. Removed the message ordering related tests since this it was no longer needed. --------- Co-authored-by: Noel Stephens * update Migrated non-authority NetworkTransform updates to the pre late update to more align with how it is handled in v1.x. Added NetworkRigidbodyBase reference to NetworkObject. For now, added the ability to add a fixed joint when parenting two NetworkObjects with NetworkTransforms and NetworkRigidbodies that both have UseRigidBodyForMotion set to true (i.e. Rigidbody has no concept of "local space" motion). * update Change the FixedJoint approach so it does not require parenting. The "parent" rigid body now updates its children in NetworkTransform when the "parent's" state is updated in order to keep both parent and children state updates synchronized on the same network tick. Added NetworkObject.CurrentParent that is set when a NetworkObject is parented. NetworkObject.NetworkTransform is now NetworkObject.NetworkTransforms (a single NetworkObject can have many NetworkTransforms). NetworkObject.NetworkRigidbodybase is now NetworkObject.NetworkRigidbodies (a single NetworkObject can have many NetworkRigidbodybase components). * fix Adding COM_UNITY_MODULES_PHYSICS wrappers around NetworkObject.NetworkRigidbodies. * fix wrapping some physics dependent properties in #if COM_UNITY_MODULES_PHYSICS. * fix Last missed NetworkRigidbodies needed wrapping in #if COM_UNITY_MODULES_PHYSICS * fix Only invoke DetachFromFixedJoint if HasAuthority is true during despawn. * fix Fixing issue where NetworkTransforms updated during FixedUpdate can have a render time that is slightly earlier than the start time. The delta between the two is typically in the nano to pico seconds range. This first pass approach to resolve this issue is to just cast the two double properties to floats prior to subtracting one from the other in order to reduce that level of granularity of the delta between the two. --------- Co-authored-by: Frank Luong <100299641+fluong6@users.noreply.github.com> * chore: add NGO 2.0.0-exp.3 version and date to changelog (#2937) adding release version and date to changelog * removing "unreleased" tag * chore: update changelog in ngo 2.0.0 exp.4 (#2939) * updating changelog * bumping ngo version in package json * adding unreleased section back --------- Co-authored-by: Noel Stephens Co-authored-by: Dominick --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 + .../Editor/NetworkManagerEditor.cs | 17 +- .../Editor/NetworkObjectEditor.cs | 7 +- .../Editor/com.unity.netcode.editor.asmdef | 5 + .../BufferedLinearInterpolator.cs | 4 +- .../Components/NetworkRigidBodyBase.cs | 224 +++++++++++++++++- .../Runtime/Components/NetworkTransform.cs | 71 +++++- .../Components/QuaternionCompressor.cs | 2 +- .../Runtime/Configuration/NetworkConfig.cs | 4 +- .../Runtime/Connection/NetworkClient.cs | 8 +- .../Runtime/Core/NetworkManager.cs | 21 +- .../Runtime/Core/NetworkObject.cs | 57 ++++- .../Runtime/Messaging/CustomMessageManager.cs | 70 +++++- .../SceneManagement/NetworkSceneManager.cs | 2 +- .../Runtime/Transports/UTP/UnityTransport.cs | 10 +- .../IntegrationTestWithApproximation.cs | 2 +- .../Runtime/NetcodeIntegrationTest.cs | 26 +- .../DistributedAuthorityCodecTests.cs | 4 +- .../Tests/Runtime/ListChangedTest.cs | 6 +- .../Runtime/NetworkBehaviourUpdaterTests.cs | 8 +- .../NetworkObjectDestroyTests.cs | 7 +- .../NetworkObjectOnSpawnTests.cs | 7 +- .../NetworkObjectOwnershipPropertiesTests.cs | 7 +- .../NetworkObjectSpawnManyObjectsTests.cs | 6 +- .../Tests/Runtime/NetworkShowHideTests.cs | 6 +- .../Tests/Runtime/NetworkSpawnManagerTests.cs | 6 +- .../Tests/Runtime/NetworkVisibilityTests.cs | 10 +- com.unity.netcode.gameobjects/package.json | 2 +- .../ClientSynchronizationValidationTest.cs | 6 +- .../InScenePlacedNetworkObjectTests.cs | 6 +- ...NetworkSceneManagerPopulateInSceneTests.cs | 6 +- .../SceneEventProgressTests.cs | 6 +- .../NetworkObjectParentingTests.cs | 18 +- .../ParentDynamicUnderInScenePlaced.cs | 6 +- .../ParentingInSceneObjectsTests.cs | 6 +- .../ParentingWorldPositionStaysTests.cs | 6 +- .../RespawnInSceneObjectsAfterShutdown.cs | 6 +- .../Runtime/RpcUserSerializableTypesTest.cs | 6 +- .../Assets/Tests/Runtime/SenderIdTests.cs | 6 +- .../Runtime/ServerDisconnectsClientTest.cs | 6 +- .../ProjectSettings/ProjectVersion.txt | 4 +- 41 files changed, 544 insertions(+), 150 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index cb1b80e2f1..2e474fc28c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -8,14 +8,18 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] +## [2.0.0-exp.4] - 2024-05-31 + ### Added +- Added `NetworkRigidbodyBase.AttachToFixedJoint` and `NetworkRigidbodyBase.DetachFromFixedJoint` to replace parenting for rigid bodies that have `NetworkRigidbodyBase.UseRigidBodyForMotion` enabled. (#2933) - Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2912) - Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2912) - Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2912) ### Fixed +- Fixed issue where non-authoritative rigid bodies with `NetworkRigidbodyBase.UseRigidBodyForMotion` enabled would constantly log errors about the renderTime being before `StartTimeConsumed`. (#2933) - Fixed issue where in-scene placed NetworkObjects could be destroyed if a client disconnects early and/or before approval. (#2924) - Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2912) - Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2898) @@ -24,6 +28,9 @@ Additional documentation and release notes are available at [Multiplayer Documen - Change all the access modifiers of test class from Public to Internal (#2930) - Changed messages are now sorted by enum values as opposed to ordinally sorting the messages by their type name. (#2929) +- Changed `NetworkClient.SessionModeTypes` to `NetworkClient.NetworkTopologyTypes`. (#2875) +- Changed `NetworkClient.SessionModeType` to `NetworkClient.NetworkTopologyType`. (#2875) +- Changed `NetworkConfig.SessionMode` to `NeworkConfig.NetworkTopology`. (#2875) ## [2.0.0-exp.2] - 2024-04-02 diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index e35db5dbf0..f420592e6f 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -30,7 +30,9 @@ public class NetworkManagerEditor : UnityEditor.Editor private SerializedProperty m_ProtocolVersionProperty; private SerializedProperty m_NetworkTransportProperty; private SerializedProperty m_TickRateProperty; - private SerializedProperty m_SessionModeProperty; +#if MULTIPLAYER_SDK_INSTALLED + private SerializedProperty m_NetworkTopologyProperty; +#endif private SerializedProperty m_ClientConnectionBufferTimeoutProperty; private SerializedProperty m_ConnectionApprovalProperty; private SerializedProperty m_EnsureNetworkVariableLengthSafetyProperty; @@ -100,7 +102,9 @@ private void Initialize() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); - m_SessionModeProperty = m_NetworkConfigProperty.FindPropertyRelative("SessionMode"); +#if MULTIPLAYER_SDK_INSTALLED + m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology"); +#endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); @@ -138,7 +142,9 @@ private void CheckNullProperties() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); - m_SessionModeProperty = m_NetworkConfigProperty.FindPropertyRelative("SessionMode"); +#if MULTIPLAYER_SDK_INSTALLED + m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology"); +#endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); @@ -177,9 +183,12 @@ public override void OnInspectorGUI() serializedObject.Update(); EditorGUILayout.PropertyField(m_RunInBackgroundProperty); EditorGUILayout.PropertyField(m_LogLevelProperty); - EditorGUILayout.PropertyField(m_SessionModeProperty); + EditorGUILayout.Space(); EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel); +#if MULTIPLAYER_SDK_INSTALLED + EditorGUILayout.PropertyField(m_NetworkTopologyProperty); +#endif EditorGUILayout.PropertyField(m_ProtocolVersionProperty); EditorGUILayout.PropertyField(m_NetworkTransportProperty); if (m_NetworkTransportProperty.objectReferenceValue == null) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index cc4b9d3982..9975482c28 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +#if MULTIPLAYER_SDK_INSTALLED using System.Linq; +#endif using UnityEditor; using UnityEngine; @@ -144,7 +146,9 @@ private void OnDestroy() } } - + // Keeping this here just in case, but it appears that in Unity 6 the visual bugs with + // enum flags is resolved +#if BYPASS_DEFAULT_ENUM_DRAWER && MULTIPLAYER_SDK_INSTALLED [CustomPropertyDrawer(typeof(NetworkObject.OwnershipStatus))] public class NetworkObjectOwnership : PropertyDrawer { @@ -188,4 +192,5 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten EditorGUI.EndProperty(); } } +#endif } diff --git a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef index 8eb62d05f4..fb85b90d0d 100644 --- a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef +++ b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef @@ -53,6 +53,11 @@ "name": "com.unity.transport", "expression": "2.0", "define": "UTP_TRANSPORT_2_0_ABOVE" + }, + { + "name": "com.unity.services.multiplayer", + "expression": "0.2.0", + "define": "MULTIPLAYER_SDK_INSTALLED" } ], "noEngineReferences": false diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index a9389424cc..543917b41e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -195,7 +195,9 @@ public T Update(float deltaTime, double renderTime, double serverTime) double range = m_EndTimeConsumed - m_StartTimeConsumed; if (range > k_SmallValue) { - t = (float)((renderTime - m_StartTimeConsumed) / range); + var rangeFactor = 1.0f / (float)range; + + t = ((float)renderTime - (float)m_StartTimeConsumed) * rangeFactor; if (t < 0.0f) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index ea9ac67b68..c2a24eb283 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -23,6 +23,7 @@ public abstract class NetworkRigidbodyBase : NetworkBehaviour /// extrapolation and is enabled, then non-authoritative instances will automatically be adjusted to use Rigidbody /// interpolation while the authoritative instance will still use extrapolation. /// + [Tooltip("When enabled and a NetworkTransform component is attached, the NetworkTransform will use the rigid body for motion and detecting changes in state.")] public bool UseRigidBodyForMotion; /// @@ -43,7 +44,7 @@ public abstract class NetworkRigidbodyBase : NetworkBehaviour private bool m_IsAuthority; private Rigidbody m_Rigidbody; private Rigidbody2D m_Rigidbody2D; - private NetworkTransform m_NetworkTransform; + internal NetworkTransform NetworkTransform; private enum InterpolationTypes { None, @@ -85,7 +86,7 @@ protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform network RigidbodyType = rigidbodyType; m_Rigidbody2D = rigidbody2D; m_Rigidbody = rigidbody; - m_NetworkTransform = networkTransform; + NetworkTransform = networkTransform; if (m_IsRigidbody2D && m_Rigidbody2D == null) { @@ -99,14 +100,14 @@ protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform network SetOriginalInterpolation(); - if (m_NetworkTransform == null) + if (NetworkTransform == null) { - m_NetworkTransform = GetComponent(); + NetworkTransform = GetComponent(); } - if (m_NetworkTransform != null) + if (NetworkTransform != null) { - m_NetworkTransform.RegisterRigidbody(this); + NetworkTransform.RegisterRigidbody(this); } else { @@ -218,7 +219,11 @@ public void MoveRotation(Quaternion rotation) { if (m_IsRigidbody2D) { - m_Rigidbody2D.MoveRotation(rotation); + var quaternion = Quaternion.identity; + var angles = quaternion.eulerAngles; + angles.z = m_Rigidbody2D.rotation; + quaternion.eulerAngles = angles; + m_Rigidbody2D.MoveRotation(quaternion); } else { @@ -381,7 +386,7 @@ public void SetIsKinematic(bool isKinematic) if (UseRigidBodyForMotion) { // Only if the NetworkTransform is set to interpolate do we need to check for extrapolation - if (m_NetworkTransform.Interpolate && m_OriginalInterpolation == InterpolationTypes.Extrapolate) + if (NetworkTransform.Interpolate && m_OriginalInterpolation == InterpolationTypes.Extrapolate) { if (IsKinematic()) { @@ -402,7 +407,7 @@ public void SetIsKinematic(bool isKinematic) } else { - SetInterpolation(m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? InterpolationTypes.None : m_OriginalInterpolation)); + SetInterpolation(m_IsAuthority ? m_OriginalInterpolation : (NetworkTransform.Interpolate ? InterpolationTypes.None : m_OriginalInterpolation)); } } @@ -477,7 +482,7 @@ internal void UpdateOwnershipAuthority() } else { - if (m_NetworkTransform.IsServerAuthoritative()) + if (NetworkTransform.IsServerAuthoritative()) { m_IsAuthority = NetworkManager.IsServer; } @@ -502,6 +507,12 @@ public override void OnNetworkSpawn() /// public override void OnNetworkDespawn() { + if (UseRigidBodyForMotion && HasAuthority) + { + DetachFromFixedJoint(); + NetworkRigidbodyConnections.Clear(); + } + // If we are automatically handling the kinematic state... if (AutoUpdateKinematicState || AutoSetKinematicOnDespawn) { @@ -512,6 +523,199 @@ public override void OnNetworkDespawn() } SetInterpolation(m_OriginalInterpolation); } + + // TODO: Possibly provide a NetworkJoint that allows for more options than fixed. + // Rigidbodies do not have the concept of "local space", and as such using a fixed joint will hold the object + // in place relative to the parent so jitter/stutter does not occur. + // Alternately, users can affix the fixed joint to a child GameObject (without a rigid body) of the parent NetworkObject + // and then add a NetworkTransform to that in order to get the parented child NetworkObject to move around in "local space" + public FixedJoint FixedJoint { get; private set; } + public FixedJoint2D FixedJoint2D { get; private set; } + + internal System.Collections.Generic.List NetworkRigidbodyConnections = new System.Collections.Generic.List(); + internal NetworkRigidbodyBase ParentBody; + + private bool m_FixedJoint2DUsingGravity; + private bool m_OriginalGravitySetting; + private float m_OriginalGravityScale; + + /// + /// When using a custom , this virtual method is invoked when the + /// is created in the event any additional adjustments are needed. + /// + protected virtual void OnFixedJointCreated() + { + + } + + /// + /// When using a custom , this virtual method is invoked when the + /// is created in the event any additional adjustments are needed. + /// + protected virtual void OnFixedJoint2DCreated() + { + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ApplyFixedJoint2D(NetworkRigidbodyBase bodyToConnect, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true) + { + transform.position = position; + m_Rigidbody2D.position = position; + m_OriginalGravitySetting = bodyToConnect.m_Rigidbody.useGravity; + m_FixedJoint2DUsingGravity = useGravity; + + if (!useGravity) + { + m_OriginalGravityScale = m_Rigidbody2D.gravityScale; + m_Rigidbody2D.gravityScale = 0.0f; + } + + if (zeroVelocity) + { + m_Rigidbody2D.velocity = Vector2.zero; + m_Rigidbody2D.angularVelocity = 0.0f; + } + + FixedJoint2D = gameObject.AddComponent(); + FixedJoint2D.connectedBody = bodyToConnect.m_Rigidbody2D; + OnFixedJoint2DCreated(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ApplyFixedJoint(NetworkRigidbodyBase bodyToConnectTo, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true) + { + transform.position = position; + m_Rigidbody.position = position; + if (zeroVelocity) + { + m_Rigidbody.linearVelocity = Vector3.zero; + m_Rigidbody.angularVelocity = Vector3.zero; + } + m_OriginalGravitySetting = m_Rigidbody.useGravity; + m_Rigidbody.useGravity = useGravity; + FixedJoint = gameObject.AddComponent(); + FixedJoint.connectedBody = bodyToConnectTo.m_Rigidbody; + FixedJoint.connectedMassScale = connectedMassScale; + FixedJoint.massScale = massScale; + OnFixedJointCreated(); + } + + + /// + /// Authority Only: + /// When invoked and not already attached to a fixed joint, this will connect two rigid bodies with enabled. + /// Invoke this method on the rigid body you wish to attach to another (i.e. weapon to player, sticky bomb to player/object, etc). + /// + /// + /// + /// + /// Parenting relative: + /// - This instance can be viewed as the child. + /// - The can be viewed as the parent. + ///
+ /// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when is enabled. + /// For more details on using and . + ///
+ /// This provides a simple joint solution between two rigid bodies and serves as an example. You can add different joint types by creating a customized/derived + /// version of . + ///
+ /// The target object to attach to. + /// The position of the connection (i.e. where you want the object to be affixed). + /// The target object's mass scale relative to this object being attached. + /// This object's mass scale relative to the target object's. + /// Determines if this object will have gravity applied to it along with the object you are connecting this one to (the default is to not use gravity for this object) + /// When true (the default), both linear and angular velocities of this object are set to zero. + /// When true (the default), this object will teleport itself to the position of connection. + /// true (success) false (failed) + public bool AttachToFixedJoint(NetworkRigidbodyBase objectToConnectTo, Vector3 positionOfConnection, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true, bool teleportObject = true) + { + if (!UseRigidBodyForMotion) + { + Debug.LogError($"[{GetType().Name}] {name} does not have {nameof(UseRigidBodyForMotion)} set! Either enable {nameof(UseRigidBodyForMotion)} on this component or do not use a {nameof(FixedJoint)} when parenting under a {nameof(NetworkObject)}."); + return false; + } + + if (IsKinematic()) + { + Debug.LogError($"[{GetType().Name}] {name} is currently kinematic! You cannot use a {nameof(FixedJoint)} with Kinematic bodies!"); + return false; + } + + if (objectToConnectTo != null) + { + if (m_IsRigidbody2D) + { + ApplyFixedJoint2D(objectToConnectTo, positionOfConnection, connectedMassScale, massScale, useGravity, zeroVelocity); + } + else + { + ApplyFixedJoint(objectToConnectTo, positionOfConnection, connectedMassScale, massScale, useGravity, zeroVelocity); + } + + ParentBody = objectToConnectTo; + ParentBody.NetworkRigidbodyConnections.Add(this); + if (teleportObject) + { + NetworkTransform.SetState(teleportDisabled: false); + } + return true; + } + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RemoveFromParentBody() + { + ParentBody.NetworkRigidbodyConnections.Remove(this); + ParentBody = null; + } + + /// + /// Authority Only: + /// When invoked and already connected to an object via or (depending upon the type of rigid body), + /// this will detach from the fixed joint and destroy the fixed joint component. + /// + /// + /// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when is enabled. + /// + public void DetachFromFixedJoint() + { + if (!HasAuthority) + { + Debug.LogError($"[{name}] Only authority can invoke {nameof(DetachFromFixedJoint)}!"); + } + if (UseRigidBodyForMotion) + { + if (m_IsRigidbody2D) + { + if (FixedJoint2D != null) + { + if (!m_FixedJoint2DUsingGravity) + { + FixedJoint2D.connectedBody.gravityScale = m_OriginalGravityScale; + } + FixedJoint2D.connectedBody = null; + Destroy(FixedJoint2D); + FixedJoint2D = null; + ResetInterpolation(); + RemoveFromParentBody(); + } + } + else + { + if (FixedJoint != null) + { + FixedJoint.connectedBody = null; + m_Rigidbody.useGravity = m_OriginalGravitySetting; + Destroy(FixedJoint); + FixedJoint = null; + ResetInterpolation(); + RemoveFromParentBody(); + } + } + } + } } } #endif // COM_UNITY_MODULES_PHYSICS diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index cd1cce2f9e..5635194563 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1556,6 +1556,14 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz NetworkLog.LogError($"[{name}] is trying to commit the transform without authority!"); return; } +#if COM_UNITY_MODULES_PHYSICS + // TODO: Make this an authority flag + // For now, just synchronize with the NetworkRigidbodyBase UseRigidBodyForMotion + if (m_NetworkRigidbodyInternal != null) + { + m_UseRigidbodyForMotion = m_NetworkRigidbodyInternal.UseRigidBodyForMotion; + } +#endif // If the transform has deltas (returns dirty) or if an explicitly set state is pending if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize)) @@ -1601,6 +1609,17 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz { m_DeltaSynch = true; } + +#if COM_UNITY_MODULES_PHYSICS + // We handle updating attached bodies when the "parent" body has a state update in order to keep their delta state updates tick synchronized. + if (m_UseRigidbodyForMotion && m_NetworkRigidbodyInternal.NetworkRigidbodyConnections.Count > 0) + { + foreach (var childRigidbody in m_NetworkRigidbodyInternal.NetworkRigidbodyConnections) + { + childRigidbody.NetworkTransform.OnNetworkTick(true); + } + } +#endif } } @@ -1690,12 +1709,23 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // All of the checks below, up to the delta position checking portion, are to determine if the // authority changed a property during runtime that requires a full synchronizing. +#if COM_UNITY_MODULES_PHYSICS + if (InLocalSpace != networkState.InLocalSpace && !m_UseRigidbodyForMotion) +#else if (InLocalSpace != networkState.InLocalSpace) +#endif { networkState.InLocalSpace = InLocalSpace; isDirty = true; networkState.IsTeleportingNextFrame = true; } +#if COM_UNITY_MODULES_PHYSICS + else if (InLocalSpace && m_UseRigidbodyForMotion) + { + // TODO: Provide more options than just FixedJoint + Debug.LogError($"[Rigidbody] WHen using a Rigidbody for motion, you cannot use {nameof(InLocalSpace)}! If parenting, use the integrated FixedJoint or use a Joint on Authority side."); + } +#endif // Check for parenting when synchronizing and/or teleporting if (isSynchronization || networkState.IsTeleportingNextFrame) @@ -1968,7 +1998,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra } if (isRotationDirty) { - networkState.Rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; + networkState.Rotation = rotation; networkState.HasRotAngleX = true; networkState.HasRotAngleY = true; networkState.HasRotAngleZ = true; @@ -2067,7 +2097,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra /// Authority subscribes to network tick events and will invoke /// each network tick. ///
- private void OnNetworkTick() + private void OnNetworkTick(bool isCalledFromParent = false) { // If not active, then ignore the update if (!gameObject.activeInHierarchy) @@ -2081,7 +2111,17 @@ private void OnNetworkTick() if (m_CachedNetworkManager.DistributedAuthorityMode && !IsOwner) { Debug.LogError($"Non-owner Client-{m_CachedNetworkManager.LocalClientId} is being updated by network tick still!!!!"); + return; } + +#if COM_UNITY_MODULES_PHYSICS + // Let the parent handle the updating of this to keep the two synchronized + if (!isCalledFromParent && m_UseRigidbodyForMotion && m_NetworkRigidbodyInternal.ParentBody != null && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) + { + return; + } +#endif + // Update any changes to the transform var transformSource = transform; OnUpdateAuthoritativeState(ref transformSource); @@ -2092,10 +2132,9 @@ private void OnNetworkTick() m_TargetPosition = GetSpaceRelativePosition(); #endif } - else // If we are no longer authority, unsubscribe to the tick event - if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) + else // If we are no longer authority, unsubscribe to the tick event { - NetworkManager.NetworkTickSystem.Tick -= OnNetworkTick; + DeregisterForTickUpdate(this); } } #endregion @@ -2125,6 +2164,14 @@ internal void UpdatePositionInterpolator(Vector3 position, double time, bool res ///
private void ApplyAuthoritativeState() { +#if COM_UNITY_MODULES_PHYSICS + // TODO: Make this an authority flag + // For now, just synchronize with the NetworkRigidbodyBase UseRigidBodyForMotion + if (m_NetworkRigidbodyInternal != null) + { + m_UseRigidbodyForMotion = m_NetworkRigidbodyInternal.UseRigidBodyForMotion; + } +#endif var networkState = m_LocalAuthoritativeNetworkState; // The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated // at the end of this method and assure that when not interpolating the non-authoritative side @@ -3249,7 +3296,7 @@ private void UpdateInterpolation() { var serverTime = m_CachedNetworkManager.ServerTime; var cachedServerTime = serverTime.Time; - var offset = 0f; + var offset = (float)serverTime.TickOffset; #if COM_UNITY_MODULES_PHYSICS var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else @@ -3258,16 +3305,13 @@ private void UpdateInterpolation() // With owner authoritative mode, non-authority clients can lag behind // by more than 1 tick period of time. The current "solution" for now // is to make their cachedRenderTime run 2 ticks behind. -#if COM_UNITY_MODULES_PHYSICS - var ticksAgo = (!IsServerAuthoritative() && !IsServer) || m_UseRigidbodyForMotion ? 2 : 1; -#else - var ticksAgo = (!IsServerAuthoritative() && !IsServer) ? 2 : 1; -#endif + var ticksAgo = (!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode ? 2 : 1; // TODO: We need an RTT that updates regularly and not only when the client sends packets //if (m_CachedNetworkManager.DistributedAuthorityMode) //{ - // ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); - // offset = m_NetworkTransformTickRegistration.Offset; + //ticksAgo = m_CachedNetworkManager.CMBServiceConnection ? 2 : 3; + //ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); + //offset = m_NetworkTransformTickRegistration.Offset; //} var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo, offset).Time; @@ -3320,6 +3364,7 @@ public virtual void OnUpdate() #if COM_UNITY_MODULES_PHYSICS + /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . diff --git a/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs b/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs index 0cdeb13ed6..f4d9b25dd2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs @@ -14,7 +14,7 @@ namespace Unity.Netcode /// M = 1.0f (which M * M would still yield 1.0f) /// w*w = M*M - (x*x + y*y + z*z) or Mathf.Sqrt(1.0f - (x*x + y*y + z*z)) /// w = Math.Sqrt(1.0f - (x*x + y*y + z*z)) - /// Using the largest the number avoids potential loss of precision in the smallest three values. + /// Using the largest number avoids potential loss of precision in the smallest three values. /// public static class QuaternionCompressor { diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 91ab3bcede..9213d24fe1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -149,8 +149,8 @@ public class NetworkConfig /// public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets) - [Tooltip("Determines if the network session will run in client-server or distributed authority mode.")] - public SessionModeTypes SessionMode; + [Tooltip("Determines whether to use the client-server or distributed authority network topology.")] + public NetworkTopologyTypes NetworkTopology; [HideInInspector] public bool UseCMBService; diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs index 63b24bd035..8cee4e0068 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs @@ -4,7 +4,7 @@ namespace Unity.Netcode { - public enum SessionModeTypes + public enum NetworkTopologyTypes { ClientServer, DistributedAuthority @@ -41,7 +41,7 @@ public class NetworkClient ///
internal bool IsApproved { get; set; } - public SessionModeTypes SessionModeType { get; internal set; } + public NetworkTopologyTypes NetworkTopologyType { get; internal set; } public bool DAHost { get; internal set; } @@ -77,9 +77,9 @@ internal bool SetRole(bool isServer, bool isClient, NetworkManager networkManage if (networkManager != null) { SpawnManager = networkManager.SpawnManager; - SessionModeType = networkManager.NetworkConfig.SessionMode; + NetworkTopologyType = networkManager.NetworkConfig.NetworkTopology; - if (SessionModeType == SessionModeTypes.DistributedAuthority) + if (NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority) { DAHost = IsClient && IsServer; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index ba7c9f3198..340eced68c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -44,7 +44,7 @@ public bool DistributedAuthorityMode { get { - return NetworkConfig.SessionMode == SessionModeTypes.DistributedAuthority; + return NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority; } } @@ -250,14 +250,9 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) NetworkTimeSystem.UpdateTime(); } break; - case NetworkUpdateStage.PostLateUpdate: + case NetworkUpdateStage.PreLateUpdate: { - // Handle deferred despawning - if (DistributedAuthorityMode) - { - SpawnManager.DeferredDespawnUpdate(ServerTime); - } - + // Non-physics based non-authority NetworkTransforms update their states after all other components foreach (var networkTransformEntry in NetworkTransformUpdate) { if (networkTransformEntry.Value.gameObject.activeInHierarchy && networkTransformEntry.Value.IsSpawned) @@ -265,6 +260,15 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) networkTransformEntry.Value.OnUpdate(); } } + } + break; + case NetworkUpdateStage.PostLateUpdate: + { + // Handle deferred despawning + if (DistributedAuthorityMode) + { + SpawnManager.DeferredDespawnUpdate(ServerTime); + } // Update any NetworkObject's registered to notify of scene migration changes. NetworkObject.UpdateNetworkObjectSceneChanges(); @@ -1047,6 +1051,7 @@ internal void Initialize(bool server) this.RegisterNetworkUpdate(NetworkUpdateStage.FixedUpdate); #endif this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); + this.RegisterNetworkUpdate(NetworkUpdateStage.PreLateUpdate); this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate); // ComponentFactory needs to set its defaults next diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 1df23e2f59..3e7ae26896 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -15,6 +15,7 @@ using UnityEngine.SceneManagement; + namespace Unity.Netcode { /// @@ -56,7 +57,33 @@ public uint PrefabIdHash } } - public NetworkTransform NetworkTransform { get; private set; } + /// + /// All component instances associated with a component instance. + /// + /// + /// When parented, all child component instances under a component instance that do not have + /// another component instance will be associated with the initial component instance. This list does not contain any parented + /// children instances with one or more component instance(s). + /// + public List NetworkTransforms { get; private set; } + +#if COM_UNITY_MODULES_PHYSICS + /// + /// All component instances associated with a component instance. + /// NOTE: This is only available if a physics package is included. If not, then this will not be available! + /// + /// + /// When parented, all child component instances under a component instance that do not have + /// another component instance will be associated with the initial component instance. This list does not contain any parented + /// child instances with one or more component instance(s). + /// + public List NetworkRigidbodies { get; private set; } +#endif + /// + /// The current parent component instance to this component instance. When there is no parent then + /// this will be . + /// + public NetworkObject CurrentParent { get; private set; } #if UNITY_EDITOR private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}"; @@ -401,8 +428,11 @@ public void DeferDespawn(int tickOffset, bool destroy = true) /// /// Determines whether a NetworkObject can be distributed to other clients during - /// a session. + /// a session. /// +#if !MULTIPLAYER_SDK_INSTALLED + [HideInInspector] +#endif [SerializeField] internal OwnershipStatus Ownership = OwnershipStatus.Distributable; @@ -1897,7 +1927,6 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent != null && (IsSpawned ^ parent.IsSpawned)) { if (NetworkManager != null && !NetworkManager.ShutdownInProgress) @@ -1910,10 +1939,12 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays if (parent == null) { + CurrentParent = null; transform.SetParent(null, worldPositionStays); } else { + CurrentParent = parent; transform.SetParent(parent.transform, worldPositionStays); } @@ -2325,8 +2356,22 @@ internal List ChildNetworkBehaviours var type = networkBehaviours[i].GetType(); if (type.IsInstanceOfType(typeof(NetworkTransform)) || type.IsSubclassOf(typeof(NetworkTransform))) { - NetworkTransform = networkBehaviours[i] as NetworkTransform; + if (NetworkTransforms == null) + { + NetworkTransforms = new List(); + } + NetworkTransforms.Add(networkBehaviours[i] as NetworkTransform); + } +#if COM_UNITY_MODULES_PHYSICS + else if (type.IsSubclassOf(typeof(NetworkRigidbodyBase))) + { + if (NetworkRigidbodies == null) + { + NetworkRigidbodies = new List(); + } + NetworkRigidbodies.Add(networkBehaviours[i] as NetworkRigidbodyBase); } +#endif } } @@ -3111,6 +3156,10 @@ internal static void UpdateNetworkObjectSceneChanges() private void Awake() { m_ChildNetworkBehaviours = null; + NetworkTransforms?.Clear(); +#if COM_UNITY_MODULES_PHYSICS + NetworkRigidbodies?.Clear(); +#endif SetCachedParent(transform.parent); SceneOrigin = gameObject.scene; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index 1b8626cd3e..dfea03e53f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -63,16 +63,39 @@ public void SendUnnamedMessageToAll(FastBufferWriter messageBuffer, NetworkDeliv /// The delivery type (QoS) to send data with public void SendUnnamedMessage(IReadOnlyList clientIds, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { - if (!m_NetworkManager.IsServer) + if (clientIds == null) { - throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client"); + throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List!"); } - if (clientIds == null) + if (!m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.IsServer) + { + if (clientIds.Count > 1 || (clientIds.Count == 1 && clientIds[0] != NetworkManager.ServerClientId)) + { + Debug.LogError("Clients cannot send unnamed messages to other clients!"); + return; + } + else if (clientIds.Count == 1) + { + SendUnnamedMessage(clientIds[0], messageBuffer, networkDelivery); + } + } + else if (m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.DAHost) + { + if (clientIds.Count > 1) + { + Debug.LogError("Sending an unnamed message to multiple clients is not yet supported in distributed authority."); + return; + } + } + + if (clientIds.Count == 0) { - throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List"); + Debug.LogError($"{nameof(clientIds)} is empty! No clients to send to."); + return; } + if (m_NetworkManager.IsHost) { for (var i = 0; i < clientIds.Count; ++i) @@ -203,6 +226,14 @@ public void RegisterNamedMessageHandler(string name, HandleNamedMessageDelegate var hash32 = XXHash.Hash32(name); var hash64 = XXHash.Hash64(name); + if (m_NetworkManager.LogLevel <= LogLevel.Developer) + { + if (m_MessageHandlerNameLookup32.ContainsKey(hash32) || m_MessageHandlerNameLookup64.ContainsKey(hash64)) + { + Debug.LogWarning($"Registering {name} named message over existing registration! Your previous registration's callback is being overwritten!"); + } + } + m_NamedMessageHandlers32[hash32] = callback; m_NamedMessageHandlers64[hash64] = callback; @@ -303,14 +334,37 @@ public void SendNamedMessage(string messageName, ulong clientId, FastBufferWrite /// The delivery type (QoS) to send data with public void SendNamedMessage(string messageName, IReadOnlyList clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { - if (!m_NetworkManager.IsServer) + if (clientIds == null) { - throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client"); + throw new ArgumentNullException(nameof(clientIds), "Client list is null! You must pass in a valid clientId list to send a named message."); } - if (clientIds == null) + if (!m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.IsServer) { - throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List"); + if (clientIds.Count > 1 || (clientIds.Count == 1 && clientIds[0] != NetworkManager.ServerClientId)) + { + Debug.LogError("Clients cannot send named messages to other clients!"); + return; + } + else if (clientIds.Count == 1) + { + SendNamedMessage(messageName, clientIds[0], messageStream, networkDelivery); + return; + } + } + else if (m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.DAHost) + { + if (clientIds.Count > 1) + { + Debug.LogError("Sending a named message to multiple clients is not yet supported in distributed authority."); + return; + } + } + + if (clientIds.Count == 0) + { + Debug.LogError($"{nameof(clientIds)} is empty! No clients to send the named message {messageName} to!"); + return; } ulong hash = 0; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 2587ed5b6f..ec41e5d0e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -425,7 +425,7 @@ public bool ActiveSceneSynchronizationEnabled /// /// Returns the currently loaded scenes that are synchronized with the session owner or server depending upon the selected - /// NetworkManager session mode. + /// network topology. /// /// /// The scenes loaded returns all scenes loaded where this returns only the scenes that have been diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index bc89e15606..6922699ddd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -426,10 +426,11 @@ private struct PacketLossCache internal static event Action TransportDisposed; internal NetworkDriver NetworkDriver => m_Driver; + protected NetworkDriver m_Driver; + private PacketLossCache m_PacketLossCache = new PacketLossCache(); private State m_State = State.Disconnected; - private NetworkDriver m_Driver; private NetworkSettings m_NetworkSettings; private ulong m_ServerClientId; @@ -554,12 +555,17 @@ private bool ClientBindAndConnect() return false; } - var serverConnection = m_Driver.Connect(serverEndpoint); + var serverConnection = Connect(serverEndpoint); m_ServerClientId = ParseClientId(serverConnection); return true; } + protected virtual NetworkConnection Connect(NetworkEndpoint serverEndpoint) + { + return m_Driver.Connect(serverEndpoint); + } + private bool ServerBindAndListen(NetworkEndpoint endPoint) { // Verify the endpoint is valid before proceeding diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs index 54f546bf33..02a5393217 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs @@ -87,7 +87,7 @@ protected Vector3 GetRandomVector3(float min, float max) return new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(min, max)); } - public IntegrationTestWithApproximation(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public IntegrationTestWithApproximation(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } public IntegrationTestWithApproximation(HostOrServer hostOrServer) : base(hostOrServer) { } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 25dd190165..3537df28cf 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -139,21 +139,21 @@ public enum HostOrServer protected bool m_UseHost = true; protected bool m_DistributedAuthority; - protected SessionModeTypes m_SessionModeType = SessionModeTypes.ClientServer; + protected NetworkTopologyTypes m_NetworkTopologyType = NetworkTopologyTypes.ClientServer; protected virtual bool UseCMBService() { return false; } - protected virtual SessionModeTypes OnGetSessionmode() + protected virtual NetworkTopologyTypes OnGetNetworkTopologyType() { - return m_SessionModeType; + return m_NetworkTopologyType; } protected void SetDistributedAuthorityProperties(NetworkManager networkManager) { - networkManager.NetworkConfig.SessionMode = m_SessionModeType; + networkManager.NetworkConfig.NetworkTopology = m_NetworkTopologyType; networkManager.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_DistributedAuthority; networkManager.NetworkConfig.UseCMBService = UseCMBService() && m_DistributedAuthority; } @@ -1567,7 +1567,7 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) Assert.IsFalse(m_ServerNetworkManager.IsListening, prefabCreateAssertError); var prefabObject = NetcodeIntegrationTestHelpers.CreateNetworkObjectPrefab(baseName, m_ServerNetworkManager, m_ClientNetworkManagers); // DANGO-TODO: Ownership flags could require us to change this - // For testing purposes, we default to true for the distribute ownership property when in distirbuted authority session mode. + // For testing purposes, we default to true for the distribute ownership property when in a distirbuted authority network topology. prefabObject.GetComponent().Ownership |= NetworkObject.OwnershipStatus.Distributable; return prefabObject; } @@ -1605,7 +1605,7 @@ private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager var newInstance = Object.Instantiate(prefabNetworkObject.gameObject); var networkObjectToSpawn = newInstance.GetComponent(); - if (owner.NetworkConfig.SessionMode == SessionModeTypes.DistributedAuthority) + if (owner.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) { networkObjectToSpawn.NetworkManagerOwner = owner; // Required to assure the client does the spawning if (isPlayerObject) @@ -1687,15 +1687,15 @@ private List SpawnObjects(NetworkObject prefabNetworkObject, Network /// public NetcodeIntegrationTest() { - m_SessionModeType = OnGetSessionmode(); - m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; + m_NetworkTopologyType = OnGetNetworkTopologyType(); + m_DistributedAuthority = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority; NetworkMessageManager.EnableMessageOrderConsoleLog = false; } - public NetcodeIntegrationTest(SessionModeTypes sessionMode) + public NetcodeIntegrationTest(NetworkTopologyTypes networkTopologyType) { - m_SessionModeType = sessionMode; - m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; + m_NetworkTopologyType = networkTopologyType; + m_DistributedAuthority = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority; } /// @@ -1717,8 +1717,8 @@ public NetcodeIntegrationTest(SessionModeTypes sessionMode) public NetcodeIntegrationTest(HostOrServer hostOrServer) { m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost; - m_SessionModeType = hostOrServer == HostOrServer.DAHost ? SessionModeTypes.DistributedAuthority : SessionModeTypes.ClientServer; - m_DistributedAuthority = OnGetSessionmode() == SessionModeTypes.DistributedAuthority; + m_NetworkTopologyType = hostOrServer == HostOrServer.DAHost ? NetworkTopologyTypes.DistributedAuthority : NetworkTopologyTypes.ClientServer; + m_DistributedAuthority = OnGetNetworkTopologyType() == NetworkTopologyTypes.DistributedAuthority; } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 39bbfce76f..390455a761 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -23,8 +23,8 @@ internal class DistributedAuthorityCodecTests : NetcodeIntegrationTest // Use the CMB Service for all tests protected override bool UseCMBService() => true; - // Set the session mode to distributed authority for all tests - protected override SessionModeTypes OnGetSessionmode() => SessionModeTypes.DistributedAuthority; + // Set the network topology to distributed authority for all tests + protected override NetworkTopologyTypes OnGetNetworkTopologyType() => NetworkTopologyTypes.DistributedAuthority; private CodecTestHooks m_ClientCodecHook; private NetworkManager Client => m_ClientNetworkManagers[0]; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs index 24a9111d40..3f47ae9fcc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ListChangedTest.cs @@ -46,8 +46,8 @@ public void Changed(NetworkListEvent listEvent) } } - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] internal class NetworkListChangedTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -57,7 +57,7 @@ internal class NetworkListChangedTests : NetcodeIntegrationTest private NetworkObject m_NetSpawnedObject1; - public NetworkListChangedTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public NetworkListChangedTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnServerAndClientsCreated() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs index 6310b7caf4..a7b0fc9244 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourUpdaterTests.cs @@ -135,7 +135,7 @@ internal struct NetVarCombinationTypes /// /// Server and Distributed Authority modes require at least 1 client while the host does not. /// - /// [Session Mode][Number of Clients][First NetVar Type][Second NetVar Type] + /// [Host or Server mode][Number of Clients][First NetVar Type][Second NetVar Type] [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.One)] [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.One, NetVarContainer.NetVarsToCheck.Two)] [TestFixture(HostOrServer.DAHost, 1, NetVarContainer.NetVarsToCheck.Two, NetVarContainer.NetVarsToCheck.Two)] @@ -202,7 +202,7 @@ protected override void OnServerAndClientsCreated() // GameObject of this prefab var netVarContainer = m_PrefabToSpawn.AddComponent(); netVarContainer.NumberOfNetVarsToCheck = m_NetVarCombinationTypes.FirstType; - if (m_SessionModeType == SessionModeTypes.DistributedAuthority) + if (m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority) { netVarContainer.SetOwnerWrite(); } @@ -210,7 +210,7 @@ protected override void OnServerAndClientsCreated() netVarContainer.ValueToSetNetVarTo = NetVarValueToSet; netVarContainer = m_PrefabToSpawn.AddComponent(); - if (m_SessionModeType == SessionModeTypes.DistributedAuthority) + if (m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority) { netVarContainer.SetOwnerWrite(); } @@ -243,7 +243,7 @@ public IEnumerator BehaviourUpdaterAllTests([Values(1, 2)] int numToSpawn) // the appropriate number of NetworkObjects with the NetVarContainer behaviour var numberOfObjectsToSpawn = numToSpawn * NumberOfClients; - var authority = m_SessionModeType == SessionModeTypes.DistributedAuthority ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; + var authority = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; // spawn the objects for (int i = 0; i < numToSpawn; i++) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 5ae711190e..64820cb626 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -14,13 +14,14 @@ namespace Unity.Netcode.RuntimeTests /// - Client destroy spawned => throw exception. /// - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] internal class NetworkObjectDestroyTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; - public NetworkObjectDestroyTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public NetworkObjectDestroyTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } /// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 205ee38204..841bad4dc7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -7,8 +7,9 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] internal class NetworkObjectOnSpawnTests : NetcodeIntegrationTest { private GameObject m_TestNetworkObjectPrefab; @@ -29,7 +30,7 @@ public enum ObserverTestTypes private const string k_WithObserversError = "Not all clients spawned the"; private const string k_WithoutObserversError = "A client spawned the"; - public NetworkObjectOnSpawnTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public NetworkObjectOnSpawnTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnServerAndClientsCreated() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs index 70b918ce8d..6ae761b360 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipPropertiesTests.cs @@ -8,8 +8,9 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] internal class NetworkObjectOwnershipPropertiesTests : NetcodeIntegrationTest { private class DummyNetworkBehaviour : NetworkBehaviour @@ -30,7 +31,7 @@ private class DummyNetworkBehaviour : NetworkBehaviour private bool m_InitialOwnerOwnedBySever; private bool m_TargetOwnerOwnedBySever; - public NetworkObjectOwnershipPropertiesTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public NetworkObjectOwnershipPropertiesTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override IEnumerator OnTearDown() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs index f684533ef2..7c8b1370e4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs @@ -7,8 +7,8 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(SessionModeTypes.ClientServer)] - [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; @@ -18,7 +18,7 @@ internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest private NetworkPrefab m_PrefabToSpawn; - public NetworkObjectSpawnManyObjectsTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public NetworkObjectSpawnManyObjectsTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } // Using this component assures we will know precisely how many prefabs were spawned on the client internal class SpawnObjecTrackingComponent : NetworkBehaviour { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 74ee054a31..ea0107384d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -126,8 +126,8 @@ public void TriggerRpc() } } - [TestFixture(SessionModeTypes.ClientServer)] - [TestFixture(SessionModeTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] internal class NetworkShowHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; @@ -143,7 +143,7 @@ internal class NetworkShowHideTests : NetcodeIntegrationTest private NetworkObject m_Object2OnClient0; private NetworkObject m_Object3OnClient0; - public NetworkShowHideTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public NetworkShowHideTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnServerAndClientsCreated() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs index f5f5e36c3b..7835bfeb20 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkSpawnManagerTests.cs @@ -52,7 +52,7 @@ public void TestClientCantAccessServerPlayer() { if (m_DistributedAuthority) { - VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_SessionModeType} mode."); + VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_NetworkTopologyType} mode."); return; } // client can't access server player @@ -77,7 +77,7 @@ public void TestClientCanAccessOtherPlayer() if (!m_DistributedAuthority) { - VerboseDebug($"Ignoring test: Clients do not have access to other player objects in {m_SessionModeType} mode."); + VerboseDebug($"Ignoring test: Clients do not have access to other player objects in {m_NetworkTopologyType} mode."); return; } @@ -90,7 +90,7 @@ public void TestClientCantAccessOtherPlayer() { if (m_DistributedAuthority) { - VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_SessionModeType} mode."); + VerboseDebug($"Ignoring test: Clients have access to other player objects in {m_NetworkTopologyType} mode."); return; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs index 7d337110e4..f81eb29de9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs @@ -7,10 +7,10 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(SceneManagementState.SceneManagementEnabled, SessionModeTypes.DistributedAuthority)] - [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.DistributedAuthority)] - [TestFixture(SceneManagementState.SceneManagementEnabled, SessionModeTypes.ClientServer)] - [TestFixture(SceneManagementState.SceneManagementDisabled, SessionModeTypes.ClientServer)] + [TestFixture(SceneManagementState.SceneManagementEnabled, NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(SceneManagementState.SceneManagementDisabled, NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(SceneManagementState.SceneManagementEnabled, NetworkTopologyTypes.ClientServer)] + [TestFixture(SceneManagementState.SceneManagementDisabled, NetworkTopologyTypes.ClientServer)] internal class NetworkVisibilityTests : NetcodeIntegrationTest { @@ -18,7 +18,7 @@ internal class NetworkVisibilityTests : NetcodeIntegrationTest private GameObject m_TestNetworkPrefab; private bool m_SceneManagementEnabled; - public NetworkVisibilityTests(SceneManagementState sceneManagementState, SessionModeTypes sessionModeType) : base(sessionModeType) + public NetworkVisibilityTests(SceneManagementState sceneManagementState, NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { m_SceneManagementEnabled = sceneManagementState == SceneManagementState.SceneManagementEnabled; } diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index df8a300be1..52c8402e5a 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0-exp.2", + "version": "2.0.0-exp.4", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs index 11f194eeed..31f54ce5de 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs @@ -9,8 +9,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class ClientSynchronizationValidationTest : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -22,7 +22,7 @@ public class ClientSynchronizationValidationTest : NetcodeIntegrationTest private bool m_RuntimeSceneWasExcludedFromSynch; private List m_ClientSceneVerifiers = new List(); - public ClientSynchronizationValidationTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public ClientSynchronizationValidationTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnNewClientStarted(NetworkManager networkManager) { diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index c2b77a4f42..446daea85a 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -13,8 +13,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class InScenePlacedNetworkObjectTests : IntegrationTestWithApproximation { protected override int NumberOfClients => 2; @@ -26,7 +26,7 @@ public class InScenePlacedNetworkObjectTests : IntegrationTestWithApproximation private bool m_CanStartServerAndClients; private string m_SceneLoading = k_SceneToLoad; - public InScenePlacedNetworkObjectTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public InScenePlacedNetworkObjectTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override IEnumerator OnSetup() { diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs index f8bb06ad0b..7f344584ea 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs @@ -11,8 +11,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -20,7 +20,7 @@ public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest protected Dictionary m_InSceneObjectList = new Dictionary(); - public NetworkSceneManagerPopulateInSceneTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public NetworkSceneManagerPopulateInSceneTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override IEnumerator OnSetup() { diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs index 3d17ce87b9..7d61f2c7dc 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventProgressTests.cs @@ -10,8 +10,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class SceneEventProgressTests : NetcodeIntegrationTest { private const string k_SceneUsedToGetAsyncOperation = "EmptyScene"; @@ -25,7 +25,7 @@ public class SceneEventProgressTests : NetcodeIntegrationTest private List m_ClientThatShouldNotHaveCompleted = new List(); private List m_ClientThatShouldHaveCompleted = new List(); - public SceneEventProgressTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public SceneEventProgressTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } private bool SceneEventProgressComplete(SceneEventProgress sceneEventProgress) { diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs index be0a1d5168..c148a05d2c 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/NetworkObjectParentingTests.cs @@ -10,8 +10,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class NetworkObjectParentingTests { private const int k_ClientInstanceCount = 1; @@ -32,10 +32,10 @@ public class NetworkObjectParentingTests private Scene m_InitScene; private Scene m_TestScene; - private SessionModeTypes m_SessionModeType; - public NetworkObjectParentingTests(SessionModeTypes sessionModeType) + private NetworkTopologyTypes m_NetworkTopologyType; + public NetworkObjectParentingTests(NetworkTopologyTypes networkTopologyType) { - m_SessionModeType = sessionModeType; + m_NetworkTopologyType = networkTopologyType; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) @@ -71,13 +71,13 @@ public IEnumerator Setup() Assert.That(m_ClientNetworkManagers, Is.Not.Null); Assert.That(m_ClientNetworkManagers.Length, Is.EqualTo(k_ClientInstanceCount)); - m_ServerNetworkManager.NetworkConfig.SessionMode = m_SessionModeType; - m_ServerNetworkManager.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_SessionModeType == SessionModeTypes.DistributedAuthority; + m_ServerNetworkManager.NetworkConfig.NetworkTopology = m_NetworkTopologyType; + m_ServerNetworkManager.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority; foreach (var client in m_ClientNetworkManagers) { - client.NetworkConfig.SessionMode = m_SessionModeType; - client.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_SessionModeType == SessionModeTypes.DistributedAuthority; + client.NetworkConfig.NetworkTopology = m_NetworkTopologyType; + client.NetworkConfig.AutoSpawnPlayerPrefabClientSide = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority; } m_Dude_NetObjs = new Transform[setCount]; diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs index e1f6dbbded..20087214c0 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs @@ -27,8 +27,8 @@ public override void OnNetworkSpawn() } } - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class ParentDynamicUnderInScenePlaced : NetcodeIntegrationTest { private const string k_SceneToLoad = "GenericInScenePlacedObject"; @@ -36,7 +36,7 @@ public class ParentDynamicUnderInScenePlaced : NetcodeIntegrationTest private GameObject m_DynamicallySpawned; private bool m_SceneIsLoaded; - public ParentDynamicUnderInScenePlaced(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public ParentDynamicUnderInScenePlaced(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override IEnumerator OnSetup() { diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index 28fe1e2d40..5b5458e17b 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -10,8 +10,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation { private const string k_BaseSceneToLoad = "UnitTestBaseScene"; @@ -24,7 +24,7 @@ public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation protected override int NumberOfClients => 2; - public ParentingInSceneObjectsTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public ParentingInSceneObjectsTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnOneTimeSetup() { diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs index 4168eea071..cd8f6a1bc7 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs @@ -11,8 +11,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class ParentingWorldPositionStaysTests : IntegrationTestWithApproximation { private const int k_NestedChildren = 10; @@ -158,7 +158,7 @@ public enum NetworkTransformSettings private Quaternion m_ChildStartRotation = Quaternion.Euler(-35.0f, 0.0f, -180.0f); private Vector3 m_ChildStartScale = Vector3.one; - public ParentingWorldPositionStaysTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public ParentingWorldPositionStaysTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override IEnumerator OnSetup() { diff --git a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs index 6fb3925b80..c8f441b6e1 100644 --- a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs +++ b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs @@ -8,8 +8,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest { public const string SceneToLoad = "InSceneNetworkObject"; @@ -17,7 +17,7 @@ public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest protected override int NumberOfClients => 0; protected Scene m_SceneLoaded; - public RespawnInSceneObjectsAfterShutdown(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public RespawnInSceneObjectsAfterShutdown(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnOneTimeSetup() { diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs index 464d3a44b6..83e327e5a7 100644 --- a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs +++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs @@ -49,8 +49,8 @@ public void NetworkSerialize(BufferSerializer } #if NGO_DAMODE - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] #endif public class RpcUserSerializableTypesTest : NetcodeIntegrationTest { @@ -84,7 +84,7 @@ public class RpcUserSerializableTypesTest : NetcodeIntegrationTest protected override int NumberOfClients => 1; #if NGO_DAMODE - public RpcUserSerializableTypesTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public RpcUserSerializableTypesTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } #endif protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() diff --git a/testproject/Assets/Tests/Runtime/SenderIdTests.cs b/testproject/Assets/Tests/Runtime/SenderIdTests.cs index e25b029b5f..d0ee7eaa30 100644 --- a/testproject/Assets/Tests/Runtime/SenderIdTests.cs +++ b/testproject/Assets/Tests/Runtime/SenderIdTests.cs @@ -11,8 +11,8 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class SenderIdTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; @@ -20,7 +20,7 @@ public class SenderIdTests : NetcodeIntegrationTest private NetworkManager FirstClient => m_ClientNetworkManagers[0]; private NetworkManager SecondClient => m_ClientNetworkManagers[1]; - public SenderIdTests(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public SenderIdTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } [UnityTest] public IEnumerator WhenSendingMessageFromServerToClient_SenderIdIsCorrect() diff --git a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs index 2cfda8d6d6..4702ba74ec 100644 --- a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs +++ b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs @@ -9,13 +9,13 @@ namespace TestProject.RuntimeTests { - [TestFixture(SessionModeTypes.DistributedAuthority)] - [TestFixture(SessionModeTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] public class ServerDisconnectsClientTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; - public ServerDisconnectsClientTest(SessionModeTypes sessionModeType) : base(sessionModeType) { } + public ServerDisconnectsClientTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnCreatePlayerPrefab() { diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index af45f2f719..f2ebefc901 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.0b13 -m_EditorVersionWithRevision: 6000.0.0b13 (21aeb48b6ed2) +m_EditorVersion: 6000.0.1f1 +m_EditorVersionWithRevision: 6000.0.1f1 (d9cf669c6271) From 98d4bbcc928bd293edf1d7956c836b6056e87d9a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 3 Jun 2024 15:02:09 -0500 Subject: [PATCH 053/236] fix: new message versioning starting after session owner message (#2942) * fix We are starting the message versioning after the more recent session owner message. * update adding change log entry * update adding PR number to changelog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 ++++ .../Runtime/Messaging/ILPPMessageProvider.cs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2e474fc28c..6f22f9c3ec 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -8,6 +8,10 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] +### Fixed + +- Fixed issue where SessionOwner message was being treated as a new entry for the new message indexing when it should have been ordinally sorted with the legacy message indices. (#2942) + ## [2.0.0-exp.4] - 2024-05-31 ### Added diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index 609cadf3ec..e51009b985 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -44,9 +44,9 @@ internal enum NetworkMessageTypes : uint SceneEvent = 17, ServerLog = 18, ServerRpc = 19, - TimeSync = 20, - Unnamed = 21, - SessionOwner = 22 + SessionOwner = 20, + TimeSync = 21, + Unnamed = 22, } From 25c7e6b7fe72a62ff064f510f34c7550a11cc565 Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 5 Jun 2024 16:13:25 -0400 Subject: [PATCH 054/236] refactor: Ensure lengths are always written as uints (#2946) * refactor: Ensure lengths are always written as uints * Update CHANGELOG.md --------- Co-authored-by: Noel Stephens --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 ++ .../Runtime/Serialization/FastBufferReader.cs | 71 ++++++++++----- .../Runtime/Serialization/FastBufferWriter.cs | 87 ++++++++++++------- 3 files changed, 111 insertions(+), 54 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 6f22f9c3ec..c7b28f5f29 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -8,10 +8,17 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] +### Added + + ### Fixed - Fixed issue where SessionOwner message was being treated as a new entry for the new message indexing when it should have been ordinally sorted with the legacy message indices. (#2942) +### Changed +- Changed `FastBufferReader` and `FastBufferWriter` so that they always ensure the length of items serialized is always serialized as an `uint` and added a check before casting for safe reading and writing.(#2946) + + ## [2.0.0-exp.4] - 2024-05-31 ### Added diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index c2cfc133bd..3612855553 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -523,8 +523,8 @@ public void ReadNetworkSerializableInPlace(ref T value) where T : INetworkSer /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void ReadValue(out string s, bool oneByteChars = false) { - ReadValue(out uint length); - s = "".PadRight((int)length); + ReadLength(out int length); + s = "".PadRight(length); int target = s.Length; fixed (char* native = s) { @@ -562,18 +562,18 @@ public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) } #endif - if (!TryBeginReadInternal(sizeof(uint))) + if (!TryBeginReadInternal(SizeOfLengthField())) { throw new OverflowException("Reading past the end of the buffer"); } - ReadValue(out uint length); + ReadLength(out int length); - if (!TryBeginReadInternal((int)length * (oneByteChars ? 1 : sizeof(char)))) + if (!TryBeginReadInternal(length * (oneByteChars ? 1 : sizeof(char)))) { throw new OverflowException("Reading past the end of the buffer"); } - s = "".PadRight((int)length); + s = "".PadRight(length); int target = s.Length; fixed (char* native = s) { @@ -592,6 +592,33 @@ public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int SizeOfLengthField() => sizeof(uint); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadLengthSafe(out uint length) => ReadUnmanagedSafe(out length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadLength(out uint length) => ReadUnmanaged(out length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadLengthSafe(out int length) + { + ReadLengthSafe(out uint temp); + if (temp > int.MaxValue) + { + throw new InvalidCastException("length value outside of int32 range"); + } + length = (int)temp; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadLength(out int length) + { + ReadLength(out uint temp); + length = (int)temp; + } + /// /// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it. /// @@ -777,7 +804,7 @@ internal unsafe void ReadUnmanagedSafe(out T value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanaged(out T[] value) where T : unmanaged { - ReadUnmanaged(out int sizeInTs); + ReadLength(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value = new T[sizeInTs]; fixed (T* ptr = value) @@ -789,7 +816,7 @@ internal unsafe void ReadUnmanaged(out T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged { - ReadUnmanagedSafe(out int sizeInTs); + ReadLengthSafe(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value = new T[sizeInTs]; fixed (T* ptr = value) @@ -801,7 +828,7 @@ internal unsafe void ReadUnmanagedSafe(out T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanaged(out NativeArray value, Allocator allocator) where T : unmanaged { - ReadUnmanaged(out int sizeInTs); + ReadLength(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value = new NativeArray(sizeInTs, allocator); byte* bytes = (byte*)value.GetUnsafePtr(); @@ -810,7 +837,7 @@ internal unsafe void ReadUnmanaged(out NativeArray value, Allocator alloca [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanagedSafe(out NativeArray value, Allocator allocator) where T : unmanaged { - ReadUnmanagedSafe(out int sizeInTs); + ReadLengthSafe(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value = new NativeArray(sizeInTs, allocator); byte* bytes = (byte*)value.GetUnsafePtr(); @@ -820,7 +847,7 @@ internal unsafe void ReadUnmanagedSafe(out NativeArray value, Allocator al [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanagedInPlace(ref NativeList value) where T : unmanaged { - ReadUnmanaged(out int sizeInTs); + ReadLength(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value.Resize(sizeInTs, NativeArrayOptions.UninitializedMemory); byte* bytes = (byte*)value.GetUnsafePtr(); @@ -829,7 +856,7 @@ internal unsafe void ReadUnmanagedInPlace(ref NativeList value) where T : [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void ReadUnmanagedSafeInPlace(ref NativeList value) where T : unmanaged { - ReadUnmanagedSafe(out int sizeInTs); + ReadLengthSafe(out int sizeInTs); int sizeInBytes = sizeInTs * sizeof(T); value.Resize(sizeInTs, NativeArrayOptions.UninitializedMemory); byte* bytes = (byte*)value.GetUnsafePtr(); @@ -1078,7 +1105,7 @@ public void ReadValueSafeInPlace(ref NativeList value, FastBufferWriter.Fo [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ReadValueSafeInPlace(ref NativeHashSet value) where T : unmanaged, IEquatable { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value.Clear(); for (var i = 0; i < length; ++i) { @@ -1093,7 +1120,7 @@ internal void ReadValueSafeInPlace(ref NativeHashMap val where TKey : unmanaged, IEquatable where TVal : unmanaged { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value.Clear(); for (var i = 0; i < length; ++i) { @@ -1553,7 +1580,7 @@ internal void ReadValueSafeInPlace(ref NativeHashMap val /// This method is a little difficult to use, since you have to know the size of the string before /// reading it, but is useful when the string is a known, fixed size. Note that the size of the /// string is also encoded, so the size to call TryBeginRead on is actually the fixed size (in bytes) - /// plus sizeof(int) + /// plus sizeof(uint) /// /// the value to read /// An unused parameter used for enabling overload resolution based on generic constraints @@ -1562,7 +1589,7 @@ internal void ReadValueSafeInPlace(ref NativeHashMap val public unsafe void ReadValue(out T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - ReadUnmanaged(out int length); + ReadLength(out int length); value = new T { Length = length @@ -1584,7 +1611,7 @@ public unsafe void ReadValue(out T value, FastBufferWriter.ForFixedStrings un public unsafe void ReadValueSafe(out T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value = new T { Length = length @@ -1606,7 +1633,7 @@ public unsafe void ReadValueSafe(out T value, FastBufferWriter.ForFixedString public unsafe void ReadValueSafeInPlace(ref T value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value.Length = length; ReadBytesSafe(value.GetUnsafePtr(), length); } @@ -1625,7 +1652,7 @@ public unsafe void ReadValueSafeInPlace(ref T value, FastBufferWriter.ForFixe public unsafe void ReadValueSafe(out NativeArray value, Allocator allocator) where T : unmanaged, INativeList, IUTF8Bytes { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value = new NativeArray(length, allocator); var ptr = (T*)value.GetUnsafePtr(); for (var i = 0; i < length; ++i) @@ -1647,7 +1674,7 @@ public unsafe void ReadValueSafe(out NativeArray value, Allocator allocato public unsafe void ReadValueSafeTemp(out NativeArray value) where T : unmanaged, INativeList, IUTF8Bytes { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value = new NativeArray(length, Allocator.Temp); var ptr = (T*)value.GetUnsafePtr(); for (var i = 0; i < length; ++i) @@ -1669,7 +1696,7 @@ public unsafe void ReadValueSafeTemp(out NativeArray value) public void ReadValueSafe(out T[] value, FastBufferWriter.ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value = new T[length]; for (var i = 0; i < length; ++i) { @@ -1691,7 +1718,7 @@ public void ReadValueSafe(out T[] value, FastBufferWriter.ForFixedStrings unu public void ReadValueSafeInPlace(ref NativeList value) where T : unmanaged, INativeList, IUTF8Bytes { - ReadUnmanagedSafe(out int length); + ReadLengthSafe(out int length); value.Resize(length, NativeArrayOptions.UninitializedMemory); for (var i = 0; i < length; ++i) { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index a6865400b7..b2a43a0a4b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -421,7 +421,7 @@ internal unsafe ArraySegment ToTempByteArray() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetWriteSize(string s, bool oneByteChars = false) { - return sizeof(int) + s.Length * (oneByteChars ? sizeof(byte) : sizeof(char)); + return SizeOfLengthField() + s.Length * (oneByteChars ? sizeof(byte) : sizeof(char)); } /// @@ -445,7 +445,7 @@ public void WriteNetworkSerializable(in T value) where T : INetworkSerializab public void WriteNetworkSerializable(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable { int sizeInTs = count != -1 ? count : array.Length - offset; - WriteValueSafe(sizeInTs); + WriteLengthSafe(sizeInTs); foreach (var item in array) { WriteNetworkSerializable(item); @@ -462,7 +462,7 @@ public void WriteNetworkSerializable(T[] array, int count = -1, int offset = public void WriteNetworkSerializable(NativeArray array, int count = -1, int offset = 0) where T : unmanaged, INetworkSerializable { int sizeInTs = count != -1 ? count : array.Length - offset; - WriteValueSafe(sizeInTs); + WriteLengthSafe(sizeInTs); foreach (var item in array) { WriteNetworkSerializable(item); @@ -480,7 +480,7 @@ public void WriteNetworkSerializable(NativeArray array, int count = -1, in public void WriteNetworkSerializable(NativeList array, int count = -1, int offset = 0) where T : unmanaged, INetworkSerializable { int sizeInTs = count != -1 ? count : array.Length - offset; - WriteValueSafe(sizeInTs); + WriteLengthSafe(sizeInTs); foreach (var item in array) { WriteNetworkSerializable(item); @@ -495,7 +495,7 @@ public void WriteNetworkSerializable(NativeList array, int count = -1, int /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void WriteValue(string s, bool oneByteChars = false) { - WriteValue((uint)s.Length); + WriteLength((uint)s.Length); int target = s.Length; if (oneByteChars) { @@ -538,7 +538,7 @@ public unsafe void WriteValueSafe(string s, bool oneByteChars = false) throw new OverflowException("Writing past the end of the buffer"); } - WriteValue((uint)s.Length); + WriteLength((uint)s.Length); int target = s.Length; if (oneByteChars) { @@ -569,7 +569,7 @@ public static unsafe int GetWriteSize(T[] array, int count = -1, int offset = { int sizeInTs = count != -1 ? count : array.Length - offset; int sizeInBytes = sizeInTs * sizeof(T); - return sizeof(int) + sizeInBytes; + return SizeOfLengthField() + sizeInBytes; } /// @@ -585,7 +585,7 @@ public static unsafe int GetWriteSize(NativeArray array, int count = -1, i { int sizeInTs = count != -1 ? count : array.Length - offset; int sizeInBytes = sizeInTs * sizeof(T); - return sizeof(int) + sizeInBytes; + return SizeOfLengthField() + sizeInBytes; } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT @@ -602,7 +602,7 @@ public static unsafe int GetWriteSize(NativeList array, int count = -1, in { int sizeInTs = count != -1 ? count : array.Length - offset; int sizeInBytes = sizeInTs * sizeof(T); - return sizeof(int) + sizeInBytes; + return SizeOfLengthField() + sizeInBytes; } #endif @@ -876,7 +876,7 @@ public static unsafe int GetWriteSize(in T value, ForStructs unused = default public static int GetWriteSize(in T value) where T : unmanaged, INativeList, IUTF8Bytes { - return value.Length + sizeof(int); + return SizeOfLengthField() + value.Length; } /// @@ -888,10 +888,10 @@ public static int GetWriteSize(in T value) public static int GetWriteSize(in NativeArray value) where T : unmanaged, INativeList, IUTF8Bytes { - var size = sizeof(int); + var size = SizeOfLengthField(); foreach (var item in value) { - size += sizeof(int) + item.Length; + size += SizeOfLengthField() + item.Length; } return size; @@ -907,10 +907,10 @@ public static int GetWriteSize(in NativeArray value) public static int GetWriteSize(in NativeList value) where T : unmanaged, INativeList, IUTF8Bytes { - var size = sizeof(int); + var size = SizeOfLengthField(); foreach (var item in value) { - size += sizeof(int) + item.Length; + size += SizeOfLengthField() + item.Length; } return size; @@ -946,10 +946,32 @@ internal unsafe void WriteUnmanagedSafe(in T value) where T : unmanaged } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int SizeOfLengthField() => sizeof(uint); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteLengthSafe(uint length) => WriteUnmanagedSafe(length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteLength(uint length) => WriteUnmanaged(length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteLengthSafe(int length) + { + if (length < 0) + { + throw new InvalidCastException("Cannot write negative length"); + } + WriteLengthSafe((uint)length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteLength(int length) => WriteLength((uint)length); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanaged(T[] value) where T : unmanaged { - WriteUnmanaged(value.Length); + WriteLength(value.Length); fixed (T* ptr = value) { byte* bytes = (byte*)ptr; @@ -959,7 +981,7 @@ internal unsafe void WriteUnmanaged(T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanagedSafe(T[] value) where T : unmanaged { - WriteUnmanagedSafe(value.Length); + WriteLengthSafe(value.Length); fixed (T* ptr = value) { byte* bytes = (byte*)ptr; @@ -970,7 +992,7 @@ internal unsafe void WriteUnmanagedSafe(T[] value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanaged(NativeArray value) where T : unmanaged { - WriteUnmanaged(value.Length); + WriteLength(value.Length); var ptr = (T*)value.GetUnsafePtr(); { byte* bytes = (byte*)ptr; @@ -980,7 +1002,7 @@ internal unsafe void WriteUnmanaged(NativeArray value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanagedSafe(NativeArray value) where T : unmanaged { - WriteUnmanagedSafe(value.Length); + WriteLengthSafe(value.Length); var ptr = (T*)value.GetUnsafePtr(); { byte* bytes = (byte*)ptr; @@ -992,7 +1014,7 @@ internal unsafe void WriteUnmanagedSafe(NativeArray value) where T : unman [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanaged(NativeList value) where T : unmanaged { - WriteUnmanaged(value.Length); + WriteLength(value.Length); #if UTP_TRANSPORT_2_0_ABOVE var ptr = value.GetUnsafePtr(); #else @@ -1006,7 +1028,7 @@ internal unsafe void WriteUnmanaged(NativeList value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteUnmanagedSafe(NativeList value) where T : unmanaged { - WriteUnmanagedSafe(value.Length); + WriteLengthSafe(value.Length); #if UTP_TRANSPORT_2_0_ABOVE var ptr = value.GetUnsafePtr(); #else @@ -1210,9 +1232,9 @@ public void WriteValue(NativeList value, ForGeneric unused = default) wher internal void WriteValueSafe(NativeHashSet value) where T : unmanaged, IEquatable { #if UTP_TRANSPORT_2_0_ABOVE - WriteUnmanagedSafe(value.Count); + WriteLengthSafe(value.Count); #else - WriteUnmanagedSafe(value.Count()); + WriteLengthSafe(value.Count()); #endif foreach (var item in value) { @@ -1227,9 +1249,9 @@ internal void WriteValueSafe(NativeHashMap value) where TVal : unmanaged { #if UTP_TRANSPORT_2_0_ABOVE - WriteUnmanagedSafe(value.Count); + WriteLengthSafe(value.Count); #else - WriteUnmanagedSafe(value.Count()); + WriteLengthSafe(value.Count()); #endif foreach (var item in value) { @@ -1765,7 +1787,8 @@ public void WriteValueSafe(NativeList value, ForGeneric unused = default) public unsafe void WriteValue(in T value, ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - WriteUnmanaged(value.Length); + // BytePacker.WriteValuePacked(this, value.Length); + WriteLength(value.Length); // This avoids a copy on the string, which could be costly for FixedString4096Bytes // Otherwise, GetUnsafePtr() is an impure function call and will result in a copy // for `in` parameters. @@ -1787,7 +1810,7 @@ public unsafe void WriteValue(in T value, ForFixedStrings unused = default) public void WriteValue(T[] value, ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - WriteUnmanaged(value.Length); + WriteLength(value.Length); foreach (var str in value) { WriteValue(str); @@ -1806,7 +1829,7 @@ public void WriteValue(T[] value, ForFixedStrings unused = default) public void WriteValue(in NativeArray value, ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - WriteUnmanaged(value.Length); + WriteLength(value.Length); foreach (var str in value) { WriteValue(str); @@ -1826,7 +1849,7 @@ public void WriteValue(in NativeArray value, ForFixedStrings unused = defa public void WriteValue(in NativeList value, ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - WriteUnmanaged(value.Length); + WriteLength(value.Length); foreach (var str in value) { WriteValue(str); @@ -1848,7 +1871,7 @@ public void WriteValue(in NativeList value, ForFixedStrings unused = defau public void WriteValueSafe(in T value, ForFixedStrings unused = default) where T : unmanaged, INativeList, IUTF8Bytes { - if (!TryBeginWriteInternal(sizeof(int) + value.Length)) + if (!TryBeginWriteInternal(SizeOfLengthField() + value.Length)) { throw new OverflowException("Writing past the end of the buffer"); } @@ -1871,7 +1894,7 @@ public void WriteValueSafe(T[] value, ForFixedStrings unused = default) { throw new OverflowException("Writing past the end of the buffer"); } - WriteUnmanaged(value.Length); + WriteLength(value.Length); foreach (var str in value) { WriteValue(str); @@ -1894,7 +1917,7 @@ public void WriteValueSafe(in NativeArray value) { throw new OverflowException("Writing past the end of the buffer"); } - WriteUnmanaged(value.Length); + WriteLength(value.Length); foreach (var str in value) { WriteValue(str); @@ -1918,7 +1941,7 @@ public void WriteValueSafe(in NativeList value) { throw new OverflowException("Writing past the end of the buffer"); } - WriteUnmanaged(value.Length); + WriteLength(value.Length); foreach (var str in value) { WriteValue(str); From d9e1a5c8986c39fb309f4b1ae4cf9b0d1a827557 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 10 Jun 2024 08:54:55 -0500 Subject: [PATCH 055/236] chore: merge release 2.0.0-exp.5 into develop-2.0.0 (#2947) update updating package version updating change log version --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c7b28f5f29..f90cad4587 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] +## [2.0.0-exp.5] - 2024-06-03 ### Added diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 52c8402e5a..c6bdde7780 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0-exp.4", + "version": "2.0.0-exp.5", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 60d321d5c01fa7a13d81fa00a3c1bc60c4afb935 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 13 Jun 2024 01:54:14 -0400 Subject: [PATCH 056/236] chore: Break up the NetworkVariableSerialization file (#2950) * reafactor: Break up the NetworkVariableSerialization file * Move related files into the new Serialization folder * Remove unrelated changes --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 58 +- .../NetworkVariableSerialization.cs | 2065 ----------------- .../NetworkVariableSerialization.cs.meta | 3 - .../NetworkVariable/Serialization.meta | 3 + .../CollectionSerializationUtility.cs | 0 .../CollectionSerializationUtility.cs.meta | 0 .../Serialization/FallbackSerializer.cs | 99 + .../Serialization/FallbackSerializer.cs.meta | 3 + .../INetworkVariableSerializer.cs | 26 + .../INetworkVariableSerializer.cs.meta | 3 + .../Serialization/NetworkVariableEquality.cs | 357 +++ .../NetworkVariableEquality.cs.meta | 3 + .../NetworkVariableSerialization.cs | 159 ++ .../NetworkVariableSerialization.cs.meta | 3 + .../{ => Serialization}/ResizableBitVector.cs | 0 .../ResizableBitVector.cs.meta | 0 .../Serialization/TypedILPPInitializers.cs | 325 +++ .../TypedILPPInitializers.cs.meta | 3 + .../TypedSerializerImplementations.cs | 1120 +++++++++ .../TypedSerializerImplementations.cs.meta | 3 + .../UserNetworkVariableSerialization.cs | 73 + .../UserNetworkVariableSerialization.cs.meta | 3 + 22 files changed, 2212 insertions(+), 2097 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs delete mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization.meta rename com.unity.netcode.gameobjects/Runtime/NetworkVariable/{ => Serialization}/CollectionSerializationUtility.cs (100%) rename com.unity.netcode.gameobjects/Runtime/NetworkVariable/{ => Serialization}/CollectionSerializationUtility.cs.meta (100%) create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs.meta rename com.unity.netcode.gameobjects/Runtime/NetworkVariable/{ => Serialization}/ResizableBitVector.cs (100%) rename com.unity.netcode.gameobjects/Runtime/NetworkVariable/{ => Serialization}/ResizableBitVector.cs.meta (100%) create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index f750f781c5..fb583e2cdd 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -721,7 +721,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb continue; } - if (networkVariableSerializationTypesTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableSerializationTypes)) + if (networkVariableSerializationTypesTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableSerializationTypedInitializers)) { networkVariableSerializationTypesTypeDef = netcodeTypeDef; continue; @@ -1007,103 +1007,103 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb switch (method.Name) { - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpy): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_UnmanagedByMemcpy): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpyArray): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_UnmanagedByMemcpyArray): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef = method; break; #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpyList): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_UnmanagedByMemcpyList): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyList_MethodRef = method; break; #endif - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializable): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_UnmanagedINetworkSerializable): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializableArray): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_UnmanagedINetworkSerializableArray): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef = method; break; #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializableList): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_UnmanagedINetworkSerializableList): m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_NativeHashSet): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_NativeHashSet): m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashSet_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_NativeHashMap): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_NativeHashMap): m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashMap_MethodRef = method; break; #endif - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_List): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_List): m_NetworkVariableSerializationTypes_InitializeSerializer_List_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_HashSet): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_HashSet): m_NetworkVariableSerializationTypes_InitializeSerializer_HashSet_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_Dictionary): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_Dictionary): m_NetworkVariableSerializationTypes_InitializeSerializer_Dictionary_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_ManagedINetworkSerializable): m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedString): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_FixedString): m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedStringArray): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_FixedStringArray): m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef = method; break; #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedStringList): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeSerializer_FixedStringList): m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringList_MethodRef = method; break; #endif - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedIEquatable): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_ManagedIEquatable): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatable): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_UnmanagedIEquatable): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatableArray): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_UnmanagedIEquatableArray): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableArray_MethodRef = method; break; #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatableList): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_UnmanagedIEquatableList): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_NativeHashSet): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_NativeHashSet): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashSet_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_NativeHashMap): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_NativeHashMap): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashMap_MethodRef = method; break; #endif - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_List): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_List): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_List_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_HashSet): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_HashSet): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_HashSet_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_Dictionary): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_Dictionary): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_Dictionary_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_UnmanagedValueEquals): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method; break; - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEqualsArray): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_UnmanagedValueEqualsArray): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef = method; break; #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEqualsList): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_UnmanagedValueEqualsList): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef = method; break; #endif - case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedClassEquals): + case nameof(NetworkVariableSerializationTypedInitializers.InitializeEqualityChecker_ManagedClassEquals): m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef = method; break; } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs deleted file mode 100644 index df7c3b925e..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ /dev/null @@ -1,2065 +0,0 @@ -using System; -using System.Collections.Generic; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Mathematics; -using UnityEditor; -using UnityEngine; - -namespace Unity.Netcode -{ - /// - /// Interface used by NetworkVariables to serialize them - /// - /// - internal interface INetworkVariableSerializer - { - // Write has to be taken by ref here because of INetworkSerializable - // Open Instance Delegates (pointers to methods without an instance attached to them) - // require the first parameter passed to them (the instance) to be passed by ref. - // So foo.Bar() becomes BarDelegate(ref foo); - // Taking T as an in parameter like we do in other places would require making a copy - // of it to pass it as a ref parameter. - public void Write(FastBufferWriter writer, ref T value); - public void Read(FastBufferReader reader, ref T value); - public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue); - public void ReadDelta(FastBufferReader reader, ref T value); - internal void ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator); - public void Duplicate(in T value, ref T duplicatedValue); - } - - /// - /// Packing serializer for shorts - /// - internal class ShortSerializer : INetworkVariableSerializer - { - public void Write(FastBufferWriter writer, ref short value) - { - BytePacker.WriteValueBitPacked(writer, value); - } - public void Read(FastBufferReader reader, ref short value) - { - ByteUnpacker.ReadValueBitPacked(reader, out value); - } - - public void WriteDelta(FastBufferWriter writer, ref short value, ref short previousValue) - { - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref short value) - { - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out short value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in short value, ref short duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Packing serializer for shorts - /// - internal class UshortSerializer : INetworkVariableSerializer - { - public void Write(FastBufferWriter writer, ref ushort value) - { - BytePacker.WriteValueBitPacked(writer, value); - } - public void Read(FastBufferReader reader, ref ushort value) - { - ByteUnpacker.ReadValueBitPacked(reader, out value); - } - - public void WriteDelta(FastBufferWriter writer, ref ushort value, ref ushort previousValue) - { - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref ushort value) - { - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ushort value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in ushort value, ref ushort duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Packing serializer for ints - /// - internal class IntSerializer : INetworkVariableSerializer - { - public void Write(FastBufferWriter writer, ref int value) - { - BytePacker.WriteValueBitPacked(writer, value); - } - public void Read(FastBufferReader reader, ref int value) - { - ByteUnpacker.ReadValueBitPacked(reader, out value); - } - - public void WriteDelta(FastBufferWriter writer, ref int value, ref int previousValue) - { - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref int value) - { - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out int value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in int value, ref int duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Packing serializer for ints - /// - internal class UintSerializer : INetworkVariableSerializer - { - public void Write(FastBufferWriter writer, ref uint value) - { - BytePacker.WriteValueBitPacked(writer, value); - } - public void Read(FastBufferReader reader, ref uint value) - { - ByteUnpacker.ReadValueBitPacked(reader, out value); - } - - public void WriteDelta(FastBufferWriter writer, ref uint value, ref uint previousValue) - { - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref uint value) - { - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out uint value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in uint value, ref uint duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Packing serializer for longs - /// - internal class LongSerializer : INetworkVariableSerializer - { - public void Write(FastBufferWriter writer, ref long value) - { - BytePacker.WriteValueBitPacked(writer, value); - } - public void Read(FastBufferReader reader, ref long value) - { - ByteUnpacker.ReadValueBitPacked(reader, out value); - } - - public void WriteDelta(FastBufferWriter writer, ref long value, ref long previousValue) - { - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref long value) - { - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out long value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in long value, ref long duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Packing serializer for longs - /// - internal class UlongSerializer : INetworkVariableSerializer - { - public void Write(FastBufferWriter writer, ref ulong value) - { - BytePacker.WriteValueBitPacked(writer, value); - } - public void Read(FastBufferReader reader, ref ulong value) - { - ByteUnpacker.ReadValueBitPacked(reader, out value); - } - - public void WriteDelta(FastBufferWriter writer, ref ulong value, ref ulong previousValue) - { - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref ulong value) - { - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ulong value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in ulong value, ref ulong duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Basic serializer for unmanaged types. - /// This covers primitives, built-in unity types, and IForceSerializeByMemcpy - /// Since all of those ultimately end up calling WriteUnmanagedSafe, this simplifies things - /// by calling that directly - thus preventing us from having to have a specific T that meets - /// the specific constraints that the various generic WriteValue calls require. - /// - /// - internal class UnmanagedTypeSerializer : INetworkVariableSerializer where T : unmanaged - { - public void Write(FastBufferWriter writer, ref T value) - { - writer.WriteUnmanagedSafe(value); - } - public void Read(FastBufferReader reader, ref T value) - { - reader.ReadUnmanagedSafe(out value); - } - - public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) - { - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref T value) - { - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in T value, ref T duplicatedValue) - { - duplicatedValue = value; - } - } - - internal class ListSerializer : INetworkVariableSerializer> - { - public void Write(FastBufferWriter writer, ref List value) - { - bool isNull = value == null; - writer.WriteValueSafe(isNull); - if (!isNull) - { - BytePacker.WriteValuePacked(writer, value.Count); - foreach (var item in value) - { - var reffable = item; - NetworkVariableSerialization.Write(writer, ref reffable); - } - } - } - public void Read(FastBufferReader reader, ref List value) - { - reader.ReadValueSafe(out bool isNull); - if (isNull) - { - value = null; - } - else - { - if (value == null) - { - value = new List(); - } - - ByteUnpacker.ReadValuePacked(reader, out int len); - if (len < value.Count) - { - value.RemoveRange(len, value.Count - len); - } - for (var i = 0; i < len; ++i) - { - // Read in place where possible - if (i < value.Count) - { - T item = value[i]; - NetworkVariableSerialization.Read(reader, ref item); - value[i] = item; - } - else - { - T item = default; - NetworkVariableSerialization.Read(reader, ref item); - value.Add(item); - } - } - } - } - - public void WriteDelta(FastBufferWriter writer, ref List value, ref List previousValue) - { - CollectionSerializationUtility.WriteListDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref List value) - { - CollectionSerializationUtility.ReadListDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out List value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in List value, ref List duplicatedValue) - { - if (duplicatedValue == null) - { - duplicatedValue = new List(); - } - - duplicatedValue.Clear(); - foreach (var item in value) - { - duplicatedValue.Add(item); - } - } - } - - internal class HashSetSerializer : INetworkVariableSerializer> where T : IEquatable - { - public void Write(FastBufferWriter writer, ref HashSet value) - { - bool isNull = value == null; - writer.WriteValueSafe(isNull); - if (!isNull) - { - writer.WriteValueSafe(value.Count); - foreach (var item in value) - { - var reffable = item; - NetworkVariableSerialization.Write(writer, ref reffable); - } - } - } - public void Read(FastBufferReader reader, ref HashSet value) - { - reader.ReadValueSafe(out bool isNull); - if (isNull) - { - value = null; - } - else - { - if (value == null) - { - value = new HashSet(); - } - else - { - value.Clear(); - } - reader.ReadValueSafe(out int len); - for (var i = 0; i < len; ++i) - { - T item = default; - NetworkVariableSerialization.Read(reader, ref item); - value.Add(item); - } - } - } - - public void WriteDelta(FastBufferWriter writer, ref HashSet value, ref HashSet previousValue) - { - CollectionSerializationUtility.WriteHashSetDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref HashSet value) - { - CollectionSerializationUtility.ReadHashSetDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out HashSet value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in HashSet value, ref HashSet duplicatedValue) - { - if (duplicatedValue == null) - { - duplicatedValue = new HashSet(); - } - - duplicatedValue.Clear(); - foreach (var item in value) - { - duplicatedValue.Add(item); - } - } - } - - - internal class DictionarySerializer : INetworkVariableSerializer> - where TKey : IEquatable - { - public void Write(FastBufferWriter writer, ref Dictionary value) - { - bool isNull = value == null; - writer.WriteValueSafe(isNull); - if (!isNull) - { - writer.WriteValueSafe(value.Count); - foreach (var item in value) - { - (var key, var val) = (item.Key, item.Value); - NetworkVariableSerialization.Write(writer, ref key); - NetworkVariableSerialization.Write(writer, ref val); - } - } - } - public void Read(FastBufferReader reader, ref Dictionary value) - { - reader.ReadValueSafe(out bool isNull); - if (isNull) - { - value = null; - } - else - { - if (value == null) - { - value = new Dictionary(); - } - else - { - value.Clear(); - } - reader.ReadValueSafe(out int len); - for (var i = 0; i < len; ++i) - { - (TKey key, TVal val) = (default, default); - NetworkVariableSerialization.Read(reader, ref key); - NetworkVariableSerialization.Read(reader, ref val); - value.Add(key, val); - } - } - } - - public void WriteDelta(FastBufferWriter writer, ref Dictionary value, ref Dictionary previousValue) - { - CollectionSerializationUtility.WriteDictionaryDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref Dictionary value) - { - CollectionSerializationUtility.ReadDictionaryDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out Dictionary value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in Dictionary value, ref Dictionary duplicatedValue) - { - if (duplicatedValue == null) - { - duplicatedValue = new Dictionary(); - } - - duplicatedValue.Clear(); - foreach (var item in value) - { - duplicatedValue.Add(item.Key, item.Value); - } - } - } - - internal class UnmanagedArraySerializer : INetworkVariableSerializer> where T : unmanaged - { - public void Write(FastBufferWriter writer, ref NativeArray value) - { - writer.WriteUnmanagedSafe(value); - } - public void Read(FastBufferReader reader, ref NativeArray value) - { - value.Dispose(); - reader.ReadUnmanagedSafe(out value, Allocator.Persistent); - } - - public void WriteDelta(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) - { - CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeArray value) - { - CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) - { - reader.ReadUnmanagedSafe(out value, allocator); - } - - public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) - { - if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) - { - if (duplicatedValue.IsCreated) - { - duplicatedValue.Dispose(); - } - - duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); - } - - duplicatedValue.CopyFrom(value); - } - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - internal class UnmanagedListSerializer : INetworkVariableSerializer> where T : unmanaged - { - public void Write(FastBufferWriter writer, ref NativeList value) - { - writer.WriteUnmanagedSafe(value); - } - public void Read(FastBufferReader reader, ref NativeList value) - { - reader.ReadUnmanagedSafeInPlace(ref value); - } - - public void WriteDelta(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) - { - CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeList value) - { - CollectionSerializationUtility.ReadNativeListDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in NativeList value, ref NativeList duplicatedValue) - { - if (!duplicatedValue.IsCreated) - { - duplicatedValue = new NativeList(value.Length, Allocator.Persistent); - } - else if (value.Length != duplicatedValue.Length) - { - duplicatedValue.ResizeUninitialized(value.Length); - } - - duplicatedValue.CopyFrom(value); - } - } - - - internal class NativeHashSetSerializer : INetworkVariableSerializer> where T : unmanaged, IEquatable - { - public void Write(FastBufferWriter writer, ref NativeHashSet value) - { - writer.WriteValueSafe(value); - } - public void Read(FastBufferReader reader, ref NativeHashSet value) - { - reader.ReadValueSafeInPlace(ref value); - } - - public void WriteDelta(FastBufferWriter writer, ref NativeHashSet value, ref NativeHashSet previousValue) - { - CollectionSerializationUtility.WriteNativeHashSetDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeHashSet value) - { - CollectionSerializationUtility.ReadNativeHashSetDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeHashSet value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in NativeHashSet value, ref NativeHashSet duplicatedValue) - { - if (!duplicatedValue.IsCreated) - { - duplicatedValue = new NativeHashSet(value.Capacity, Allocator.Persistent); - } - - duplicatedValue.Clear(); - foreach (var item in value) - { - duplicatedValue.Add(item); - } - } - } - - - internal class NativeHashMapSerializer : INetworkVariableSerializer> - where TKey : unmanaged, IEquatable - where TVal : unmanaged - { - public void Write(FastBufferWriter writer, ref NativeHashMap value) - { - writer.WriteValueSafe(value); - } - public void Read(FastBufferReader reader, ref NativeHashMap value) - { - reader.ReadValueSafeInPlace(ref value); - } - - public void WriteDelta(FastBufferWriter writer, ref NativeHashMap value, ref NativeHashMap previousValue) - { - CollectionSerializationUtility.WriteNativeHashMapDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeHashMap value) - { - CollectionSerializationUtility.ReadNativeHashMapDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeHashMap value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in NativeHashMap value, ref NativeHashMap duplicatedValue) - { - if (!duplicatedValue.IsCreated) - { - duplicatedValue = new NativeHashMap(value.Capacity, Allocator.Persistent); - } - - duplicatedValue.Clear(); - foreach (var item in value) - { - duplicatedValue.Add(item.Key, item.Value); - } - } - } -#endif - - /// - /// Serializer for FixedStrings - /// - /// - internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged, INativeList, IUTF8Bytes - { - public void Write(FastBufferWriter writer, ref T value) - { - writer.WriteValueSafe(value); - } - public void Read(FastBufferReader reader, ref T value) - { - reader.ReadValueSafeInPlace(ref value); - } - - // Because of how strings are generally used, it is likely that most strings will still write as full strings - // instead of deltas. This actually adds one byte to the data to encode that it was serialized in full. - // But the potential savings from a small change to a large string are valuable enough to be worth that extra - // byte. - public unsafe void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) - { - using var changes = new ResizableBitVector(Allocator.Temp); - int minLength = math.min(value.Length, previousValue.Length); - var numChanges = 0; - for (var i = 0; i < minLength; ++i) - { - var val = value[i]; - var prevVal = previousValue[i]; - if (!NetworkVariableSerialization.AreEqual(ref val, ref prevVal)) - { - ++numChanges; - changes.Set(i); - } - } - - for (var i = previousValue.Length; i < value.Length; ++i) - { - ++numChanges; - changes.Set(i); - } - - if (changes.GetSerializedSize() + FastBufferWriter.GetWriteSize() * numChanges > FastBufferWriter.GetWriteSize() * value.Length) - { - writer.WriteByteSafe(1); - writer.WriteValueSafe(value); - return; - } - writer.WriteByte(0); - BytePacker.WriteValuePacked(writer, value.Length); - writer.WriteValueSafe(changes); - unsafe - { - byte* ptr = value.GetUnsafePtr(); - byte* prevPtr = previousValue.GetUnsafePtr(); - for (int i = 0; i < value.Length; ++i) - { - if (changes.IsSet(i)) - { - if (i < previousValue.Length) - { - NetworkVariableSerialization.WriteDelta(writer, ref ptr[i], ref prevPtr[i]); - } - else - { - NetworkVariableSerialization.Write(writer, ref ptr[i]); - } - } - } - } - } - public unsafe void ReadDelta(FastBufferReader reader, ref T value) - { - // Writing can use the NativeArray logic as it is, but reading is a little different. - // Using the NativeArray logic for reading would result in length changes allocating a new NativeArray, - // which is not what we want for FixedString. With FixedString, the actual size of the data does not change, - // only an in-memory "length" value - so if the length changes, the only thing we want to do is change - // that value, and otherwise read everything in-place. - reader.ReadByteSafe(out byte full); - if (full == 1) - { - reader.ReadValueSafeInPlace(ref value); - return; - } - ByteUnpacker.ReadValuePacked(reader, out int length); - var changes = new ResizableBitVector(Allocator.Temp); - using var toDispose = changes; - { - reader.ReadNetworkSerializableInPlace(ref changes); - - value.Length = length; - - byte* ptr = value.GetUnsafePtr(); - for (var i = 0; i < value.Length; ++i) - { - if (changes.IsSet(i)) - { - reader.ReadByte(out ptr[i]); - } - } - } - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in T value, ref T duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Serializer for FixedStrings - /// - /// - internal class FixedStringArraySerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes - { - public void Write(FastBufferWriter writer, ref NativeArray value) - { - writer.WriteValueSafe(value); - } - public void Read(FastBufferReader reader, ref NativeArray value) - { - value.Dispose(); - reader.ReadValueSafe(out value, Allocator.Persistent); - } - - - public void WriteDelta(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) - { - CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeArray value) - { - CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) - { - reader.ReadValueSafe(out value, allocator); - } - - public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) - { - if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) - { - if (duplicatedValue.IsCreated) - { - duplicatedValue.Dispose(); - } - - duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); - } - - duplicatedValue.CopyFrom(value); - } - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - /// - /// Serializer for FixedStrings - /// - /// - internal class FixedStringListSerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes - { - public void Write(FastBufferWriter writer, ref NativeList value) - { - writer.WriteValueSafe(value); - } - public void Read(FastBufferReader reader, ref NativeList value) - { - reader.ReadValueSafeInPlace(ref value); - } - - public void WriteDelta(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) - { - CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeList value) - { - CollectionSerializationUtility.ReadNativeListDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in NativeList value, ref NativeList duplicatedValue) - { - if (!duplicatedValue.IsCreated) - { - duplicatedValue = new NativeList(value.Length, Allocator.Persistent); - } - else if (value.Length != duplicatedValue.Length) - { - duplicatedValue.ResizeUninitialized(value.Length); - } - - duplicatedValue.CopyFrom(value); - } - } -#endif - - /// - /// Serializer for unmanaged INetworkSerializable types - /// - /// - internal class UnmanagedNetworkSerializableSerializer : INetworkVariableSerializer where T : unmanaged, INetworkSerializable - { - public void Write(FastBufferWriter writer, ref T value) - { - var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); - value.NetworkSerialize(bufferSerializer); - } - public void Read(FastBufferReader reader, ref T value) - { - var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); - value.NetworkSerialize(bufferSerializer); - } - - public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) - { - if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) - { - UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); - return; - } - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref T value) - { - if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) - { - UserNetworkVariableSerialization.ReadDelta(reader, ref value); - return; - } - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in T value, ref T duplicatedValue) - { - duplicatedValue = value; - } - } - - /// - /// Serializer for unmanaged INetworkSerializable types - /// - /// - internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable - { - public void Write(FastBufferWriter writer, ref NativeArray value) - { - writer.WriteNetworkSerializable(value); - } - public void Read(FastBufferReader reader, ref NativeArray value) - { - value.Dispose(); - reader.ReadNetworkSerializable(out value, Allocator.Persistent); - } - - - public void WriteDelta(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) - { - CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeArray value) - { - CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) - { - reader.ReadNetworkSerializable(out value, allocator); - } - - public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) - { - if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) - { - if (duplicatedValue.IsCreated) - { - duplicatedValue.Dispose(); - } - - duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); - } - - duplicatedValue.CopyFrom(value); - } - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - /// - /// Serializer for unmanaged INetworkSerializable types - /// - /// - internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable - { - public void Write(FastBufferWriter writer, ref NativeList value) - { - writer.WriteNetworkSerializable(value); - } - public void Read(FastBufferReader reader, ref NativeList value) - { - reader.ReadNetworkSerializableInPlace(ref value); - } - - public void WriteDelta(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) - { - CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue); - } - public void ReadDelta(FastBufferReader reader, ref NativeList value) - { - CollectionSerializationUtility.ReadNativeListDelta(reader, ref value); - } - - void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in NativeList value, ref NativeList duplicatedValue) - { - if (!duplicatedValue.IsCreated) - { - duplicatedValue = new NativeList(value.Length, Allocator.Persistent); - } - else if (value.Length != duplicatedValue.Length) - { - duplicatedValue.ResizeUninitialized(value.Length); - } - - duplicatedValue.CopyFrom(value); - } - } -#endif - - /// - /// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it - /// has to be null-aware - /// - internal class ManagedNetworkSerializableSerializer : INetworkVariableSerializer where T : class, INetworkSerializable, new() - { - public void Write(FastBufferWriter writer, ref T value) - { - var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); - bool isNull = (value == null); - bufferSerializer.SerializeValue(ref isNull); - if (!isNull) - { - value.NetworkSerialize(bufferSerializer); - } - } - public void Read(FastBufferReader reader, ref T value) - { - var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); - bool isNull = false; - bufferSerializer.SerializeValue(ref isNull); - if (isNull) - { - value = null; - } - else - { - if (value == null) - { - value = new T(); - } - value.NetworkSerialize(bufferSerializer); - } - } - - public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) - { - if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) - { - UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); - return; - } - Write(writer, ref value); - } - public void ReadDelta(FastBufferReader reader, ref T value) - { - if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) - { - UserNetworkVariableSerialization.ReadDelta(reader, ref value); - return; - } - Read(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in T value, ref T duplicatedValue) - { - using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); - var refValue = value; - Write(writer, ref refValue); - - using var reader = new FastBufferReader(writer, Allocator.None); - Read(reader, ref duplicatedValue); - } - } - - /// - /// This class is used to register user serialization with NetworkVariables for types - /// that are serialized via user serialization, such as with FastBufferReader and FastBufferWriter - /// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows - /// users to tell NetworkVariable about those extension methods (or simply pass in a lambda) - /// - /// - public class UserNetworkVariableSerialization - { - /// - /// The write value delegate handler definition - /// - /// The to write the value of type `T` - /// The value of type `T` to be written - public delegate void WriteValueDelegate(FastBufferWriter writer, in T value); - - /// - /// The write value delegate handler definition - /// - /// The to write the value of type `T` - /// The value of type `T` to be written - public delegate void WriteDeltaDelegate(FastBufferWriter writer, in T value, in T previousValue); - - /// - /// The read value delegate handler definition - /// - /// The to read the value of type `T` - /// The value of type `T` to be read - public delegate void ReadValueDelegate(FastBufferReader reader, out T value); - - /// - /// The read value delegate handler definition - /// - /// The to read the value of type `T` - /// The value of type `T` to be read - public delegate void ReadDeltaDelegate(FastBufferReader reader, ref T value); - - /// - /// The read value delegate handler definition - /// - /// The to read the value of type `T` - /// The value of type `T` to be read - public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue); - - /// - /// Callback to write a value - /// - public static WriteValueDelegate WriteValue; - - /// - /// Callback to read a value - /// - public static ReadValueDelegate ReadValue; - - /// - /// Callback to write a delta between two values, based on computing the difference between the previous and - /// current values. - /// - public static WriteDeltaDelegate WriteDelta; - - /// - /// Callback to read a delta, applying only select changes to the current value. - /// - public static ReadDeltaDelegate ReadDelta; - - /// - /// Callback to create a duplicate of a value, used to check for dirty status. - /// - public static DuplicateValueDelegate DuplicateValue; - } - - /// - /// This class is instantiated for types that we can't determine ahead of time are serializable - types - /// that don't meet any of the constraints for methods that are available on FastBufferReader and - /// FastBufferWriter. These types may or may not be serializable through extension methods. To ensure - /// the user has time to pass in the delegates to UserNetworkVariableSerialization, the existence - /// of user serialization isn't checked until it's used, so if no serialization is provided, this - /// will throw an exception when an object containing the relevant NetworkVariable is spawned. - /// - /// - internal class FallbackSerializer : INetworkVariableSerializer - { - private void ThrowArgumentError() - { - throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); - } - - public void Write(FastBufferWriter writer, ref T value) - { - if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) - { - ThrowArgumentError(); - } - UserNetworkVariableSerialization.WriteValue(writer, value); - } - public void Read(FastBufferReader reader, ref T value) - { - if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) - { - ThrowArgumentError(); - } - UserNetworkVariableSerialization.ReadValue(reader, out value); - } - - public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) - { - if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) - { - ThrowArgumentError(); - } - - if (UserNetworkVariableSerialization.WriteDelta == null || UserNetworkVariableSerialization.ReadDelta == null) - { - UserNetworkVariableSerialization.WriteValue(writer, value); - return; - } - UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); - } - - public void ReadDelta(FastBufferReader reader, ref T value) - { - if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) - { - ThrowArgumentError(); - } - - if (UserNetworkVariableSerialization.WriteDelta == null || UserNetworkVariableSerialization.ReadDelta == null) - { - UserNetworkVariableSerialization.ReadValue(reader, out value); - return; - } - UserNetworkVariableSerialization.ReadDelta(reader, ref value); - } - - void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) - { - throw new NotImplementedException(); - } - - public void Duplicate(in T value, ref T duplicatedValue) - { - if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) - { - ThrowArgumentError(); - } - UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); - } - } - - /// - /// This class contains initialization functions for various different types used in NetworkVariables. - /// Generally speaking, these methods are called by a module initializer created by codegen (NetworkBehaviourILPP) - /// and do not need to be called manually. - /// - /// There are two types of initializers: Serializers and EqualityCheckers. Every type must have an EqualityChecker - /// registered to it in order to be used in NetworkVariable; however, not all types need a Serializer. Types without - /// a serializer registered will fall back to using the delegates in . - /// If no such delegate has been registered, a type without a serializer will throw an exception on the first attempt - /// to serialize or deserialize it. (Again, however, codegen handles this automatically and this registration doesn't - /// typically need to be performed manually.) - /// - public static class NetworkVariableSerializationTypes - { - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] -#if UNITY_EDITOR - [InitializeOnLoadMethod] -#endif - internal static void InitializeIntegerSerialization() - { - NetworkVariableSerialization.Serializer = new ShortSerializer(); - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; - NetworkVariableSerialization.Serializer = new UshortSerializer(); - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; - NetworkVariableSerialization.Serializer = new IntSerializer(); - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; - NetworkVariableSerialization.Serializer = new UintSerializer(); - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; - NetworkVariableSerialization.Serializer = new LongSerializer(); - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; - NetworkVariableSerialization.Serializer = new UlongSerializer(); - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; - - // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server - NetworkVariableSerialization.Type = CollectionItemType.Short; - NetworkVariableSerialization.Type = CollectionItemType.UShort; - NetworkVariableSerialization.Type = CollectionItemType.Int; - NetworkVariableSerialization.Type = CollectionItemType.UInt; - NetworkVariableSerialization.Type = CollectionItemType.Long; - NetworkVariableSerialization.Type = CollectionItemType.ULong; - } - - /// - /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer - /// - /// - public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged - { - NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); - // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server - NetworkVariableSerialization.Type = CollectionItemType.Unmanaged; - } - - /// - /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer - /// - /// - public static void InitializeSerializer_UnmanagedByMemcpyArray() where T : unmanaged - { - NetworkVariableSerialization>.Serializer = new UnmanagedArraySerializer(); - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - /// - /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer - /// - /// - public static void InitializeSerializer_UnmanagedByMemcpyList() where T : unmanaged - { - NetworkVariableSerialization>.Serializer = new UnmanagedListSerializer(); - } - - /// - /// Registeres a native hash set (this generic implementation works with all types) - /// - /// - public static void InitializeSerializer_NativeHashSet() where T : unmanaged, IEquatable - { - NetworkVariableSerialization>.Serializer = new NativeHashSetSerializer(); - } - - /// - /// Registeres a native hash set (this generic implementation works with all types) - /// - /// - public static void InitializeSerializer_NativeHashMap() - where TKey : unmanaged, IEquatable - where TVal : unmanaged - { - NetworkVariableSerialization>.Serializer = new NativeHashMapSerializer(); - } -#endif - - /// - /// Registeres a native hash set (this generic implementation works with all types) - /// - /// - public static void InitializeSerializer_List() - { - NetworkVariableSerialization>.Serializer = new ListSerializer(); - } - - /// - /// Registeres a native hash set (this generic implementation works with all types) - /// - /// - public static void InitializeSerializer_HashSet() where T : IEquatable - { - NetworkVariableSerialization>.Serializer = new HashSetSerializer(); - } - - /// - /// Registeres a native hash set (this generic implementation works with all types) - /// - /// - public static void InitializeSerializer_Dictionary() where TKey : IEquatable - { - NetworkVariableSerialization>.Serializer = new DictionarySerializer(); - } - - /// - /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to - /// NetworkSerialize - /// - /// - public static void InitializeSerializer_UnmanagedINetworkSerializable() where T : unmanaged, INetworkSerializable - { - NetworkVariableSerialization.Serializer = new UnmanagedNetworkSerializableSerializer(); - } - - /// - /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to - /// NetworkSerialize - /// - /// - public static void InitializeSerializer_UnmanagedINetworkSerializableArray() where T : unmanaged, INetworkSerializable - { - NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableArraySerializer(); - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - /// - /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to - /// NetworkSerialize - /// - /// - public static void InitializeSerializer_UnmanagedINetworkSerializableList() where T : unmanaged, INetworkSerializable - { - NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableListSerializer(); - } -#endif - - /// - /// Registers a managed type that implements INetworkSerializable and will be serialized through a call to - /// NetworkSerialize - /// - /// - public static void InitializeSerializer_ManagedINetworkSerializable() where T : class, INetworkSerializable, new() - { - NetworkVariableSerialization.Serializer = new ManagedNetworkSerializableSerializer(); - } - - /// - /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString - /// serializers - /// - /// - public static void InitializeSerializer_FixedString() where T : unmanaged, INativeList, IUTF8Bytes - { - NetworkVariableSerialization.Serializer = new FixedStringSerializer(); - } - - /// - /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString - /// serializers - /// - /// - public static void InitializeSerializer_FixedStringArray() where T : unmanaged, INativeList, IUTF8Bytes - { - NetworkVariableSerialization>.Serializer = new FixedStringArraySerializer(); - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - /// - /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString - /// serializers - /// - /// - public static void InitializeSerializer_FixedStringList() where T : unmanaged, INativeList, IUTF8Bytes - { - NetworkVariableSerialization>.Serializer = new FixedStringListSerializer(); - } -#endif - - /// - /// Registers a managed type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_ManagedIEquatable() where T : class, IEquatable - { - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.EqualityEqualsObject; - } - - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_UnmanagedIEquatable() where T : unmanaged, IEquatable - { - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.EqualityEquals; - } - - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_UnmanagedIEquatableArray() where T : unmanaged, IEquatable - { - NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsArray; - } - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_List() - { - NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsList; - } - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_HashSet() where T : IEquatable - { - NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsHashSet; - } - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_Dictionary() - where TKey : IEquatable - { - NetworkVariableSerialization>.AreEqual = NetworkVariableDictionarySerialization.GenericEqualsDictionary; - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_UnmanagedIEquatableList() where T : unmanaged, IEquatable - { - NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsNativeList; - } - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_NativeHashSet() where T : unmanaged, IEquatable - { - NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.EqualityEqualsNativeHashSet; - } - /// - /// Registers an unmanaged type that will be checked for equality using T.Equals() - /// - /// - public static void InitializeEqualityChecker_NativeHashMap() - where TKey : unmanaged, IEquatable - where TVal : unmanaged - { - NetworkVariableSerialization>.AreEqual = NetworkVariableMapSerialization.GenericEqualsNativeHashMap; - } -#endif - - /// - /// Registers an unmanaged type that will be checked for equality using memcmp and only considered - /// equal if they are bitwise equivalent in memory - /// - /// - public static void InitializeEqualityChecker_UnmanagedValueEquals() where T : unmanaged - { - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ValueEquals; - } - - /// - /// Registers an unmanaged type that will be checked for equality using memcmp and only considered - /// equal if they are bitwise equivalent in memory - /// - /// - public static void InitializeEqualityChecker_UnmanagedValueEqualsArray() where T : unmanaged - { - NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.ValueEqualsArray; - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - /// - /// Registers an unmanaged type that will be checked for equality using memcmp and only considered - /// equal if they are bitwise equivalent in memory - /// - /// - public static void InitializeEqualityChecker_UnmanagedValueEqualsList() where T : unmanaged - { - NetworkVariableSerialization>.AreEqual = NetworkVariableSerialization.ValueEqualsList; - } -#endif - - /// - /// Registers a managed type that will be checked for equality using the == operator - /// - /// - public static void InitializeEqualityChecker_ManagedClassEquals() where T : class - { - NetworkVariableSerialization.AreEqual = NetworkVariableSerialization.ClassEquals; - } - } - - /// - /// Support methods for reading/writing NetworkVariables - /// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints, - /// but there's no way to achieve the same thing with a class, this sets up various read/write schemes - /// based on which constraints are met by `T` using reflection, which is done at module load time. - /// - /// The type the associated NetworkVariable is templated on - [Serializable] - public static class NetworkVariableSerialization - { - internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); - - /// - /// The collection item type tells the CMB server how to read the bytes of each item in the collection - /// - /// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server - internal static CollectionItemType Type = CollectionItemType.Unknown; - - /// - /// A callback to check if two values are equal. - /// - public delegate bool EqualsDelegate(ref T a, ref T b); - - /// - /// Uses the most efficient mechanism for a given type to determine if two values are equal. - /// For types that implement , it will call the Equals() method. - /// For unmanaged types, it will do a bytewise memory comparison. - /// For other types, it will call the == operator. - ///
- ///
- /// Note: If you are using this in a custom generic class, please make sure your class is - /// decorated with so that codegen can - /// initialize the serialization mechanisms correctly. If your class is NOT - /// generic, it is better to check their equality yourself. - ///
- public static EqualsDelegate AreEqual { get; internal set; } - - /// - /// Serialize a value using the best-known serialization method for a generic value. - /// Will reliably serialize any value that is passed to it correctly with no boxing. - ///
- ///
- /// Note: If you are using this in a custom generic class, please make sure your class is - /// decorated with so that codegen can - /// initialize the serialization mechanisms correctly. If your class is NOT - /// generic, it is better to use FastBufferWriter directly. - ///
- ///
- /// If the codegen is unable to determine a serializer for a type, - /// . is called, which, by default, - /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. - ///
- /// - /// - public static void Write(FastBufferWriter writer, ref T value) - { - Serializer.Write(writer, ref value); - } - - /// - /// Deserialize a value using the best-known serialization method for a generic value. - /// Will reliably deserialize any value that is passed to it correctly with no boxing. - /// For types whose deserialization can be determined by codegen (which is most types), - /// GC will only be incurred if the type is a managed type and the ref value passed in is `null`, - /// in which case a new value is created; otherwise, it will be deserialized in-place. - ///
- ///
- /// Note: If you are using this in a custom generic class, please make sure your class is - /// decorated with so that codegen can - /// initialize the serialization mechanisms correctly. If your class is NOT - /// generic, it is better to use FastBufferReader directly. - ///
- ///
- /// If the codegen is unable to determine a serializer for a type, - /// . is called, which, by default, - /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. - ///
- /// - /// - public static void Read(FastBufferReader reader, ref T value) - { - Serializer.Read(reader, ref value); - } - - /// - /// Serialize a value using the best-known serialization method for a generic value. - /// Will reliably serialize any value that is passed to it correctly with no boxing. - ///
- ///
- /// Note: If you are using this in a custom generic class, please make sure your class is - /// decorated with so that codegen can - /// initialize the serialization mechanisms correctly. If your class is NOT - /// generic, it is better to use FastBufferWriter directly. - ///
- ///
- /// If the codegen is unable to determine a serializer for a type, - /// . is called, which, by default, - /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. - ///
- /// - /// - public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) - { - Serializer.WriteDelta(writer, ref value, ref previousValue); - } - - /// - /// Deserialize a value using the best-known serialization method for a generic value. - /// Will reliably deserialize any value that is passed to it correctly with no boxing. - /// For types whose deserialization can be determined by codegen (which is most types), - /// GC will only be incurred if the type is a managed type and the ref value passed in is `null`, - /// in which case a new value is created; otherwise, it will be deserialized in-place. - ///
- ///
- /// Note: If you are using this in a custom generic class, please make sure your class is - /// decorated with so that codegen can - /// initialize the serialization mechanisms correctly. If your class is NOT - /// generic, it is better to use FastBufferReader directly. - ///
- ///
- /// If the codegen is unable to determine a serializer for a type, - /// . is called, which, by default, - /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. - ///
- /// - /// - public static void ReadDelta(FastBufferReader reader, ref T value) - { - Serializer.ReadDelta(reader, ref value); - } - - /// - /// Duplicates a value using the most efficient means of creating a complete copy. - /// For most types this is a simple assignment or memcpy. - /// For managed types, this is will serialize and then deserialize the value to ensure - /// a correct copy. - ///
- ///
- /// Note: If you are using this in a custom generic class, please make sure your class is - /// decorated with so that codegen can - /// initialize the serialization mechanisms correctly. If your class is NOT - /// generic, it is better to duplicate it directly. - ///
- ///
- /// If the codegen is unable to determine a serializer for a type, - /// . is called, which, by default, - /// will throw an exception, unless you have assigned a user duplication callback to it at runtime. - ///
- /// - /// - public static void Duplicate(in T value, ref T duplicatedValue) - { - Serializer.Duplicate(value, ref duplicatedValue); - } - - // Compares two values of the same unmanaged type by underlying memory - // Ignoring any overridden value checks - // Size is fixed - internal static unsafe bool ValueEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged - { - // get unmanaged pointers - var aptr = UnsafeUtility.AddressOf(ref a); - var bptr = UnsafeUtility.AddressOf(ref b); - - // compare addresses - return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0; - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - // Compares two values of the same unmanaged type by underlying memory - // Ignoring any overridden value checks - // Size is fixed - internal static unsafe bool ValueEqualsList(ref NativeList a, ref NativeList b) where TValueType : unmanaged - { - if (a.IsCreated != b.IsCreated) - { - return false; - } - - if (!a.IsCreated) - { - return true; - } - - if (a.Length != b.Length) - { - return false; - } - -#if UTP_TRANSPORT_2_0_ABOVE - var aptr = a.GetUnsafePtr(); - var bptr = b.GetUnsafePtr(); -#else - var aptr = (TValueType*)a.GetUnsafePtr(); - var bptr = (TValueType*)b.GetUnsafePtr(); -#endif - - return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 0; - } -#endif - - // Compares two values of the same unmanaged type by underlying memory - // Ignoring any overridden value checks - // Size is fixed - internal static unsafe bool ValueEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged - { - if (a.IsCreated != b.IsCreated) - { - return false; - } - - if (!a.IsCreated) - { - return true; - } - - if (a.Length != b.Length) - { - return false; - } - - var aptr = (TValueType*)a.GetUnsafePtr(); - var bptr = (TValueType*)b.GetUnsafePtr(); - return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 0; - } - - internal static bool EqualityEqualsObject(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable - { - if (a == null) - { - return b == null; - } - - if (b == null) - { - return false; - } - - return a.Equals(b); - } - - internal static bool EqualityEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable - { - return a.Equals(b); - } - - internal static bool EqualityEqualsList(ref List a, ref List b) - { - if ((a == null) != (b == null)) - { - return false; - } - - if (a == null) - { - return true; - } - - if (a.Count != b.Count) - { - return false; - } - - for (var i = 0; i < a.Count; ++i) - { - var aItem = a[i]; - var bItem = b[i]; - if (!NetworkVariableSerialization.AreEqual(ref aItem, ref bItem)) - { - return false; - } - } - - return true; - } - - internal static bool EqualityEqualsHashSet(ref HashSet a, ref HashSet b) where TValueType : IEquatable - { - if ((a == null) != (b == null)) - { - return false; - } - - if (a == null) - { - return true; - } - - if (a.Count != b.Count) - { - return false; - } - - foreach (var item in a) - { - if (!b.Contains(item)) - { - return false; - } - } - - return true; - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - // Compares two values of the same unmanaged type by underlying memory - // Ignoring any overridden value checks - // Size is fixed - internal static unsafe bool EqualityEqualsNativeList(ref NativeList a, ref NativeList b) where TValueType : unmanaged, IEquatable - { - if (a.IsCreated != b.IsCreated) - { - return false; - } - - if (!a.IsCreated) - { - return true; - } - - if (a.Length != b.Length) - { - return false; - } - -#if UTP_TRANSPORT_2_0_ABOVE - var aptr = a.GetUnsafePtr(); - var bptr = b.GetUnsafePtr(); -#else - var aptr = (TValueType*)a.GetUnsafePtr(); - var bptr = (TValueType*)b.GetUnsafePtr(); -#endif - for (var i = 0; i < a.Length; ++i) - { - if (!EqualityEquals(ref aptr[i], ref bptr[i])) - { - return false; - } - } - - return true; - } - - internal static bool EqualityEqualsNativeHashSet(ref NativeHashSet a, ref NativeHashSet b) where TValueType : unmanaged, IEquatable - { - if (a.IsCreated != b.IsCreated) - { - return false; - } - - if (!a.IsCreated) - { - return true; - } - -#if UTP_TRANSPORT_2_0_ABOVE - if (a.Count != b.Count) -#else - if (a.Count() != b.Count()) -#endif - { - return false; - } - - foreach (var item in a) - { - if (!b.Contains(item)) - { - return false; - } - } - - return true; - } -#endif - - // Compares two values of the same unmanaged type by underlying memory - // Ignoring any overridden value checks - // Size is fixed - internal static unsafe bool EqualityEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged, IEquatable - { - if (a.IsCreated != b.IsCreated) - { - return false; - } - - if (!a.IsCreated) - { - return true; - } - - if (a.Length != b.Length) - { - return false; - } - - var aptr = (TValueType*)a.GetUnsafePtr(); - var bptr = (TValueType*)b.GetUnsafePtr(); - for (var i = 0; i < a.Length; ++i) - { - if (!EqualityEquals(ref aptr[i], ref bptr[i])) - { - return false; - } - } - - return true; - } - - internal static bool ClassEquals(ref TValueType a, ref TValueType b) where TValueType : class - { - return a == b; - } - } - internal class NetworkVariableDictionarySerialization - where TKey : IEquatable - { - - internal static bool GenericEqualsDictionary(ref Dictionary a, ref Dictionary b) - { - if ((a == null) != (b == null)) - { - return false; - } - - if (a == null) - { - return true; - } - - if (a.Count != b.Count) - { - return false; - } - - foreach (var item in a) - { - var hasKey = b.TryGetValue(item.Key, out var val); - if (!hasKey) - { - return false; - } - - var bVal = item.Value; - if (!NetworkVariableSerialization.AreEqual(ref bVal, ref val)) - { - return false; - } - } - - return true; - } - } - -#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT - internal class NetworkVariableMapSerialization - where TKey : unmanaged, IEquatable - where TVal : unmanaged - { - - internal static bool GenericEqualsNativeHashMap(ref NativeHashMap a, ref NativeHashMap b) - { - if (a.IsCreated != b.IsCreated) - { - return false; - } - - if (!a.IsCreated) - { - return true; - } - -#if UTP_TRANSPORT_2_0_ABOVE - if (a.Count != b.Count) -#else - if (a.Count() != b.Count()) -#endif - { - return false; - } - - foreach (var item in a) - { - var hasKey = b.TryGetValue(item.Key, out var val); - if (!hasKey || !NetworkVariableSerialization.AreEqual(ref item.Value, ref val)) - { - return false; - } - } - - return true; - } - } -#endif - - // RuntimeAccessModifiersILPP will make this `public` - // This is just pass-through to NetworkVariableSerialization but is here becaues I could not get ILPP - // to generate code that would successfully call Type.Method(T), but it has no problem calling Type.Method(T) - internal class RpcFallbackSerialization - { - public static void Write(FastBufferWriter writer, ref T value) - { - NetworkVariableSerialization.Write(writer, ref value); - } - - public static void Read(FastBufferReader reader, ref T value) - { - NetworkVariableSerialization.Read(reader, ref value); - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs.meta deleted file mode 100644 index 7ab0efc436..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 2c6ef5fdf2e94ec3b4ce8086d52700b3 -timeCreated: 1650985453 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization.meta new file mode 100644 index 0000000000..4937ff6d52 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d960ae6c5b8241aa9e2906b709095ea1 +timeCreated: 1718215841 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs rename to com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/NetworkVariable/CollectionSerializationUtility.cs.meta rename to com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs new file mode 100644 index 0000000000..5e855745c0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs @@ -0,0 +1,99 @@ +using System; +using Unity.Collections; + +namespace Unity.Netcode +{ + /// + /// This class is instantiated for types that we can't determine ahead of time are serializable - types + /// that don't meet any of the constraints for methods that are available on FastBufferReader and + /// FastBufferWriter. These types may or may not be serializable through extension methods. To ensure + /// the user has time to pass in the delegates to UserNetworkVariableSerialization, the existence + /// of user serialization isn't checked until it's used, so if no serialization is provided, this + /// will throw an exception when an object containing the relevant NetworkVariable is spawned. + /// + /// + internal class FallbackSerializer : INetworkVariableSerializer + { + private void ThrowArgumentError() + { + throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + } + + public void Write(FastBufferWriter writer, ref T value) + { + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) + { + ThrowArgumentError(); + } + UserNetworkVariableSerialization.WriteValue(writer, value); + } + public void Read(FastBufferReader reader, ref T value) + { + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) + { + ThrowArgumentError(); + } + UserNetworkVariableSerialization.ReadValue(reader, out value); + } + + public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) + { + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) + { + ThrowArgumentError(); + } + + if (UserNetworkVariableSerialization.WriteDelta == null || UserNetworkVariableSerialization.ReadDelta == null) + { + UserNetworkVariableSerialization.WriteValue(writer, value); + return; + } + UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref T value) + { + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) + { + ThrowArgumentError(); + } + + if (UserNetworkVariableSerialization.WriteDelta == null || UserNetworkVariableSerialization.ReadDelta == null) + { + UserNetworkVariableSerialization.ReadValue(reader, out value); + return; + } + UserNetworkVariableSerialization.ReadDelta(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) + { + ThrowArgumentError(); + } + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + } + } + + // RuntimeAccessModifiersILPP will make this `public` + // This is just pass-through to NetworkVariableSerialization but is here because I could not get ILPP + // to generate code that would successfully call Type.Method(T), but it has no problem calling Type.Method(T) + internal class RpcFallbackSerialization + { + public static void Write(FastBufferWriter writer, ref T value) + { + NetworkVariableSerialization.Write(writer, ref value); + } + + public static void Read(FastBufferReader reader, ref T value) + { + NetworkVariableSerialization.Read(reader, ref value); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs.meta new file mode 100644 index 0000000000..4472d153c4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 288dbe7d1ff74860ae3552c034485538 +timeCreated: 1718219109 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs new file mode 100644 index 0000000000..f462e52238 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs @@ -0,0 +1,26 @@ +using Unity.Collections; + +namespace Unity.Netcode +{ + /// + /// Interface used by NetworkVariables to serialize them + /// + /// + /// + internal interface INetworkVariableSerializer + { + // Write has to be taken by ref here because of INetworkSerializable + // Open Instance Delegates (pointers to methods without an instance attached to them) + // require the first parameter passed to them (the instance) to be passed by ref. + // So foo.Bar() becomes BarDelegate(ref foo); + // Taking T as an in parameter like we do in other places would require making a copy + // of it to pass it as a ref parameter., + + public void Write(FastBufferWriter writer, ref T value); + public void Read(FastBufferReader reader, ref T value); + public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue); + public void ReadDelta(FastBufferReader reader, ref T value); + internal void ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator); + public void Duplicate(in T value, ref T duplicatedValue); + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs.meta new file mode 100644 index 0000000000..58433bb58a --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f78e258ef55f4ee89bc3f24d67b8d242 +timeCreated: 1718218205 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs new file mode 100644 index 0000000000..c8aecb3869 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs @@ -0,0 +1,357 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Netcode; + +namespace Unity.Netcode +{ + internal static class NetworkVariableEquality + { + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool ValueEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged + { + // get unmanaged pointers + var aptr = UnsafeUtility.AddressOf(ref a); + var bptr = UnsafeUtility.AddressOf(ref b); + + // compare addresses + return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0; + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool ValueEqualsList(ref NativeList a, ref NativeList b) where TValueType : unmanaged + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + +#if UTP_TRANSPORT_2_0_ABOVE + var aptr = a.GetUnsafePtr(); + var bptr = b.GetUnsafePtr(); +#else + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); +#endif + + return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 0; + } +#endif + + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool ValueEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); + return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType) * a.Length) == 0; + } + + internal static bool EqualityEqualsObject(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable + { + if (a == null) + { + return b == null; + } + + if (b == null) + { + return false; + } + + return a.Equals(b); + } + + internal static bool EqualityEquals(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable + { + return a.Equals(b); + } + + internal static bool EqualityEqualsList(ref List a, ref List b) + { + if (a == null != (b == null)) + { + return false; + } + + if (a == null) + { + return true; + } + + if (a.Count != b.Count) + { + return false; + } + + for (var i = 0; i < a.Count; ++i) + { + var aItem = a[i]; + var bItem = b[i]; + if (!NetworkVariableSerialization.AreEqual(ref aItem, ref bItem)) + { + return false; + } + } + + return true; + } + + internal static bool EqualityEqualsHashSet(ref HashSet a, ref HashSet b) where TValueType : IEquatable + { + if (a == null != (b == null)) + { + return false; + } + + if (a == null) + { + return true; + } + + if (a.Count != b.Count) + { + return false; + } + + foreach (var item in a) + { + if (!b.Contains(item)) + { + return false; + } + } + + return true; + } + + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool EqualityEqualsArray(ref NativeArray a, ref NativeArray b) where TValueType : unmanaged, IEquatable + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); + for (var i = 0; i < a.Length; ++i) + { + if (!EqualityEquals(ref aptr[i], ref bptr[i])) + { + return false; + } + } + + return true; + } + + internal static bool ClassEquals(ref TValueType a, ref TValueType b) where TValueType : class + { + return a == b; + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + // Compares two values of the same unmanaged type by underlying memory + // Ignoring any overridden value checks + // Size is fixed + internal static unsafe bool EqualityEqualsNativeList(ref NativeList a, ref NativeList b) where TValueType : unmanaged, IEquatable + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + + if (a.Length != b.Length) + { + return false; + } + +#if UTP_TRANSPORT_2_0_ABOVE + var aptr = a.GetUnsafePtr(); + var bptr = b.GetUnsafePtr(); +#else + var aptr = (TValueType*)a.GetUnsafePtr(); + var bptr = (TValueType*)b.GetUnsafePtr(); +#endif + for (var i = 0; i < a.Length; ++i) + { + if (!EqualityEquals(ref aptr[i], ref bptr[i])) + { + return false; + } + } + + return true; + } + + internal static bool EqualityEqualsNativeHashSet(ref NativeHashSet a, ref NativeHashSet b) where TValueType : unmanaged, IEquatable + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + +#if UTP_TRANSPORT_2_0_ABOVE + if (a.Count != b.Count) +#else + if (a.Count() != b.Count()) +#endif + { + return false; + } + + foreach (var item in a) + { + if (!b.Contains(item)) + { + return false; + } + } + + return true; + } +#endif + } +} + +/// +/// Support methods for equality of NetworkVariable collection types. +/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints, +/// but there's no way to achieve the same thing with a class, this sets up various read/write schemes +/// based on which constraints are met by `T` using reflection, which is done at module load time. +/// +/// The type the associated NetworkVariable dictionary collection key templated on +/// The type the associated NetworkVariable dictionary collection value templated on +internal class NetworkVariableDictionarySerialization + where TKey : IEquatable +{ + internal static bool GenericEqualsDictionary(ref Dictionary a, ref Dictionary b) + { + if (a == null != (b == null)) + { + return false; + } + + if (a == null) + { + return true; + } + + if (a.Count != b.Count) + { + return false; + } + + foreach (var item in a) + { + var hasKey = b.TryGetValue(item.Key, out var val); + if (!hasKey) + { + return false; + } + + var bVal = item.Value; + if (!NetworkVariableSerialization.AreEqual(ref bVal, ref val)) + { + return false; + } + } + + return true; + } +} + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT +internal class NetworkVariableMapSerialization + where TKey : unmanaged, IEquatable + where TVal : unmanaged +{ + internal static bool GenericEqualsNativeHashMap(ref NativeHashMap a, ref NativeHashMap b) + { + if (a.IsCreated != b.IsCreated) + { + return false; + } + + if (!a.IsCreated) + { + return true; + } + +#if UTP_TRANSPORT_2_0_ABOVE + if (a.Count != b.Count) +#else + if (a.Count() != b.Count()) +#endif + { + return false; + } + + foreach (var item in a) + { + var hasKey = b.TryGetValue(item.Key, out var val); + if (!hasKey || !NetworkVariableSerialization.AreEqual(ref item.Value, ref val)) + { + return false; + } + } + + return true; + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs.meta new file mode 100644 index 0000000000..9a91970355 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 24b8352a975044509931bf684ccfdb82 +timeCreated: 1718219366 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs new file mode 100644 index 0000000000..683266050b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -0,0 +1,159 @@ +using System; + +namespace Unity.Netcode +{ + /// + /// Support methods for reading/writing NetworkVariables + /// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints, + /// but there's no way to achieve the same thing with a class, this sets up various read/write schemes + /// based on which constraints are met by `T` using reflection, which is done at module load time. + /// + /// The type the associated NetworkVariable is templated on + [Serializable] + public static class NetworkVariableSerialization + { + internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); + + /// + /// The collection item type tells the CMB server how to read the bytes of each item in the collection + /// + /// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server + internal static CollectionItemType Type = CollectionItemType.Unknown; + + /// + /// A callback to check if two values are equal. + /// + public delegate bool EqualsDelegate(ref T a, ref T b); + + /// + /// Uses the most efficient mechanism for a given type to determine if two values are equal. + /// For types that implement , it will call the Equals() method. + /// For unmanaged types, it will do a bytewise memory comparison. + /// For other types, it will call the == operator. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to check their equality yourself. + ///
+ public static EqualsDelegate AreEqual { get; internal set; } + /// + /// Serialize a value using the best-known serialization method for a generic value. + /// Will reliably serialize any value that is passed to it correctly with no boxing. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to use FastBufferWriter directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. + ///
+ /// + /// + public static void Write(FastBufferWriter writer, ref T value) + { + Serializer.Write(writer, ref value); + } + + /// + /// Deserialize a value using the best-known serialization method for a generic value. + /// Will reliably deserialize any value that is passed to it correctly with no boxing. + /// For types whose deserialization can be determined by codegen (which is most types), + /// GC will only be incurred if the type is a managed type and the ref value passed in is `null`, + /// in which case a new value is created; otherwise, it will be deserialized in-place. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to use FastBufferReader directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. + ///
+ /// + /// + public static void Read(FastBufferReader reader, ref T value) + { + Serializer.Read(reader, ref value); + } + + /// + /// Serialize a value using the best-known serialization method for a generic value. + /// Will reliably serialize any value that is passed to it correctly with no boxing. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to use FastBufferWriter directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. + ///
+ /// + /// + public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) + { + Serializer.WriteDelta(writer, ref value, ref previousValue); + } + + /// + /// Deserialize a value using the best-known serialization method for a generic value. + /// Will reliably deserialize any value that is passed to it correctly with no boxing. + /// For types whose deserialization can be determined by codegen (which is most types), + /// GC will only be incurred if the type is a managed type and the ref value passed in is `null`, + /// in which case a new value is created; otherwise, it will be deserialized in-place. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to use FastBufferReader directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. + ///
+ /// + /// + public static void ReadDelta(FastBufferReader reader, ref T value) + { + Serializer.ReadDelta(reader, ref value); + } + + /// + /// Duplicates a value using the most efficient means of creating a complete copy. + /// For most types this is a simple assignment or memcpy. + /// For managed types, this is will serialize and then deserialize the value to ensure + /// a correct copy. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to duplicate it directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user duplication callback to it at runtime. + ///
+ /// + /// + public static void Duplicate(in T value, ref T duplicatedValue) + { + Serializer.Duplicate(value, ref duplicatedValue); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs.meta new file mode 100644 index 0000000000..d7b821f95b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7a943170e35746e8913dd494d79bb63d +timeCreated: 1718215899 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs rename to com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/NetworkVariable/ResizableBitVector.cs.meta rename to com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs new file mode 100644 index 0000000000..f3e679adeb --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using UnityEditor; +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// This class contains initialization functions for various different types used in NetworkVariables. + /// Generally speaking, these methods are called by a module initializer created by codegen (NetworkBehaviourILPP) + /// and do not need to be called manually. + /// + /// There are two types of initializers: Serializers and EqualityCheckers. Every type must have an EqualityChecker + /// registered to it in order to be used in NetworkVariable; however, not all types need a Serializer. Types without + /// a serializer registered will fall back to using the delegates in . + /// If no such delegate has been registered, a type without a serializer will throw an exception on the first attempt + /// to serialize or deserialize it. (Again, however, codegen handles this automatically and this registration doesn't + /// typically need to be performed manually.) + /// + public static class NetworkVariableSerializationTypedInitializers + { + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] +#if UNITY_EDITOR + [InitializeOnLoadMethod] +#endif + internal static void InitializeIntegerSerialization() + { + NetworkVariableSerialization.Serializer = new ShortSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; + NetworkVariableSerialization.Serializer = new UshortSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; + NetworkVariableSerialization.Serializer = new IntSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; + NetworkVariableSerialization.Serializer = new UintSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; + NetworkVariableSerialization.Serializer = new LongSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; + NetworkVariableSerialization.Serializer = new UlongSerializer(); + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; + + // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server + NetworkVariableSerialization.Type = CollectionItemType.Short; + NetworkVariableSerialization.Type = CollectionItemType.UShort; + NetworkVariableSerialization.Type = CollectionItemType.Int; + NetworkVariableSerialization.Type = CollectionItemType.UInt; + NetworkVariableSerialization.Type = CollectionItemType.Long; + NetworkVariableSerialization.Type = CollectionItemType.ULong; + } + + /// + /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer + /// + /// + public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged + { + NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); + // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server + NetworkVariableSerialization.Type = CollectionItemType.Unmanaged; + } + + /// + /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer + /// + /// + public static void InitializeSerializer_UnmanagedByMemcpyArray() where T : unmanaged + { + NetworkVariableSerialization>.Serializer = new UnmanagedArraySerializer(); + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + /// + /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer + /// + /// + public static void InitializeSerializer_UnmanagedByMemcpyList() where T : unmanaged + { + NetworkVariableSerialization>.Serializer = new UnmanagedListSerializer(); + } + + /// + /// Registeres a native hash set (this generic implementation works with all types) + /// + /// + public static void InitializeSerializer_NativeHashSet() where T : unmanaged, IEquatable + { + NetworkVariableSerialization>.Serializer = new NativeHashSetSerializer(); + } + + /// + /// Registeres a native hash set (this generic implementation works with all types) + /// + /// + public static void InitializeSerializer_NativeHashMap() + where TKey : unmanaged, IEquatable + where TVal : unmanaged + { + NetworkVariableSerialization>.Serializer = new NativeHashMapSerializer(); + } +#endif + + /// + /// Registeres a native hash set (this generic implementation works with all types) + /// + /// + public static void InitializeSerializer_List() + { + NetworkVariableSerialization>.Serializer = new ListSerializer(); + } + + /// + /// Registeres a native hash set (this generic implementation works with all types) + /// + /// + public static void InitializeSerializer_HashSet() where T : IEquatable + { + NetworkVariableSerialization>.Serializer = new HashSetSerializer(); + } + + /// + /// Registeres a native hash set (this generic implementation works with all types) + /// + /// + public static void InitializeSerializer_Dictionary() where TKey : IEquatable + { + NetworkVariableSerialization>.Serializer = new DictionarySerializer(); + } + + /// + /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_UnmanagedINetworkSerializable() where T : unmanaged, INetworkSerializable + { + NetworkVariableSerialization.Serializer = new UnmanagedNetworkSerializableSerializer(); + } + + /// + /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_UnmanagedINetworkSerializableArray() where T : unmanaged, INetworkSerializable + { + NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableArraySerializer(); + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + /// + /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_UnmanagedINetworkSerializableList() where T : unmanaged, INetworkSerializable + { + NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableListSerializer(); + } +#endif + + /// + /// Registers a managed type that implements INetworkSerializable and will be serialized through a call to + /// NetworkSerialize + /// + /// + public static void InitializeSerializer_ManagedINetworkSerializable() where T : class, INetworkSerializable, new() + { + NetworkVariableSerialization.Serializer = new ManagedNetworkSerializableSerializer(); + } + + /// + /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString + /// serializers + /// + /// + public static void InitializeSerializer_FixedString() where T : unmanaged, INativeList, IUTF8Bytes + { + NetworkVariableSerialization.Serializer = new FixedStringSerializer(); + } + + /// + /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString + /// serializers + /// + /// + public static void InitializeSerializer_FixedStringArray() where T : unmanaged, INativeList, IUTF8Bytes + { + NetworkVariableSerialization>.Serializer = new FixedStringArraySerializer(); + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + /// + /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString + /// serializers + /// + /// + public static void InitializeSerializer_FixedStringList() where T : unmanaged, INativeList, IUTF8Bytes + { + NetworkVariableSerialization>.Serializer = new FixedStringListSerializer(); + } +#endif + + /// + /// Registers a managed type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_ManagedIEquatable() where T : class, IEquatable + { + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.EqualityEqualsObject; + } + + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_UnmanagedIEquatable() where T : unmanaged, IEquatable + { + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.EqualityEquals; + } + + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_UnmanagedIEquatableArray() where T : unmanaged, IEquatable + { + NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsArray; + } + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_List() + { + NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsList; + } + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_HashSet() where T : IEquatable + { + NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsHashSet; + } + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_Dictionary() + where TKey : IEquatable + { + NetworkVariableSerialization>.AreEqual = NetworkVariableDictionarySerialization.GenericEqualsDictionary; + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_UnmanagedIEquatableList() where T : unmanaged, IEquatable + { + NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsNativeList; + } + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_NativeHashSet() where T : unmanaged, IEquatable + { + NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsNativeHashSet; + } + /// + /// Registers an unmanaged type that will be checked for equality using T.Equals() + /// + /// + public static void InitializeEqualityChecker_NativeHashMap() + where TKey : unmanaged, IEquatable + where TVal : unmanaged + { + NetworkVariableSerialization>.AreEqual = NetworkVariableMapSerialization.GenericEqualsNativeHashMap; + } +#endif + + /// + /// Registers an unmanaged type that will be checked for equality using memcmp and only considered + /// equal if they are bitwise equivalent in memory + /// + /// + public static void InitializeEqualityChecker_UnmanagedValueEquals() where T : unmanaged + { + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; + } + + /// + /// Registers an unmanaged type that will be checked for equality using memcmp and only considered + /// equal if they are bitwise equivalent in memory + /// + /// + public static void InitializeEqualityChecker_UnmanagedValueEqualsArray() where T : unmanaged + { + NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.ValueEqualsArray; + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + /// + /// Registers an unmanaged type that will be checked for equality using memcmp and only considered + /// equal if they are bitwise equivalent in memory + /// + /// + public static void InitializeEqualityChecker_UnmanagedValueEqualsList() where T : unmanaged + { + NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.ValueEqualsList; + } +#endif + + /// + /// Registers a managed type that will be checked for equality using the == operator + /// + /// + public static void InitializeEqualityChecker_ManagedClassEquals() where T : class + { + NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ClassEquals; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs.meta new file mode 100644 index 0000000000..6d1ebba80d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 65bdb3e11a9a412ab5e936a9c96a3da0 +timeCreated: 1718216842 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs new file mode 100644 index 0000000000..91d3a60b5e --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -0,0 +1,1120 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Mathematics; + +namespace Unity.Netcode +{ + /// + /// Packing serializer for shorts + /// + internal class ShortSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref short value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + + public void Read(FastBufferReader reader, ref short value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + + public void WriteDelta(FastBufferWriter writer, ref short value, ref short previousValue) + { + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref short value) + { + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out short value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in short value, ref short duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Packing serializer for shorts + /// + internal class UshortSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref ushort value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + + public void Read(FastBufferReader reader, ref ushort value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + + public void WriteDelta(FastBufferWriter writer, ref ushort value, ref ushort previousValue) + { + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref ushort value) + { + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ushort value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in ushort value, ref ushort duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Packing serializer for ints + /// + internal class IntSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref int value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + + public void Read(FastBufferReader reader, ref int value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + + public void WriteDelta(FastBufferWriter writer, ref int value, ref int previousValue) + { + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref int value) + { + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out int value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in int value, ref int duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Packing serializer for ints + /// + internal class UintSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref uint value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + + public void Read(FastBufferReader reader, ref uint value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + + public void WriteDelta(FastBufferWriter writer, ref uint value, ref uint previousValue) + { + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref uint value) + { + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out uint value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in uint value, ref uint duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Packing serializer for longs + /// + internal class LongSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref long value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + + public void Read(FastBufferReader reader, ref long value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + + public void WriteDelta(FastBufferWriter writer, ref long value, ref long previousValue) + { + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref long value) + { + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out long value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in long value, ref long duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Packing serializer for longs + /// + internal class UlongSerializer : INetworkVariableSerializer + { + public void Write(FastBufferWriter writer, ref ulong value) + { + BytePacker.WriteValueBitPacked(writer, value); + } + + public void Read(FastBufferReader reader, ref ulong value) + { + ByteUnpacker.ReadValueBitPacked(reader, out value); + } + + public void WriteDelta(FastBufferWriter writer, ref ulong value, ref ulong previousValue) + { + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref ulong value) + { + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out ulong value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in ulong value, ref ulong duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Basic serializer for unmanaged types. + /// This covers primitives, built-in unity types, and IForceSerializeByMemcpy + /// Since all of those ultimately end up calling WriteUnmanagedSafe, this simplifies things + /// by calling that directly - thus preventing us from having to have a specific T that meets + /// the specific constraints that the various generic WriteValue calls require. + /// + /// + internal class UnmanagedTypeSerializer : INetworkVariableSerializer where T : unmanaged + { + public void Write(FastBufferWriter writer, ref T value) + { + writer.WriteUnmanagedSafe(value); + } + + public void Read(FastBufferReader reader, ref T value) + { + reader.ReadUnmanagedSafe(out value); + } + + public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) + { + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref T value) + { + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + duplicatedValue = value; + } + } + + internal class ListSerializer : INetworkVariableSerializer> + { + public void Write(FastBufferWriter writer, ref List value) + { + var isNull = value == null; + writer.WriteValueSafe(isNull); + if (!isNull) + { + BytePacker.WriteValuePacked(writer, value.Count); + foreach (var item in value) + { + var reffable = item; + NetworkVariableSerialization.Write(writer, ref reffable); + } + } + } + + public void Read(FastBufferReader reader, ref List value) + { + reader.ReadValueSafe(out bool isNull); + if (isNull) + { + value = null; + } + else + { + if (value == null) + { + value = new List(); + } + + ByteUnpacker.ReadValuePacked(reader, out int len); + if (len < value.Count) + { + value.RemoveRange(len, value.Count - len); + } + + for (var i = 0; i < len; ++i) + { + // Read in place where possible + if (i < value.Count) + { + var item = value[i]; + NetworkVariableSerialization.Read(reader, ref item); + value[i] = item; + } + else + { + T item = default; + NetworkVariableSerialization.Read(reader, ref item); + value.Add(item); + } + } + } + } + + public void WriteDelta(FastBufferWriter writer, ref List value, ref List previousValue) + { + CollectionSerializationUtility.WriteListDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref List value) + { + CollectionSerializationUtility.ReadListDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out List value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in List value, ref List duplicatedValue) + { + if (duplicatedValue == null) + { + duplicatedValue = new List(); + } + + duplicatedValue.Clear(); + foreach (var item in value) + { + duplicatedValue.Add(item); + } + } + } + + internal class HashSetSerializer : INetworkVariableSerializer> where T : IEquatable + { + public void Write(FastBufferWriter writer, ref HashSet value) + { + var isNull = value == null; + writer.WriteValueSafe(isNull); + if (!isNull) + { + writer.WriteValueSafe(value.Count); + foreach (var item in value) + { + var reffable = item; + NetworkVariableSerialization.Write(writer, ref reffable); + } + } + } + + + public void Read(FastBufferReader reader, ref HashSet value) + { + reader.ReadValueSafe(out bool isNull); + if (isNull) + { + value = null; + } + else + { + if (value == null) + { + value = new HashSet(); + } + else + { + value.Clear(); + } + + reader.ReadValueSafe(out int len); + for (var i = 0; i < len; ++i) + { + T item = default; + NetworkVariableSerialization.Read(reader, ref item); + value.Add(item); + } + } + } + + public void WriteDelta(FastBufferWriter writer, ref HashSet value, ref HashSet previousValue) + { + CollectionSerializationUtility.WriteHashSetDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref HashSet value) + { + CollectionSerializationUtility.ReadHashSetDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out HashSet value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in HashSet value, ref HashSet duplicatedValue) + { + if (duplicatedValue == null) + { + duplicatedValue = new HashSet(); + } + + duplicatedValue.Clear(); + foreach (var item in value) + { + duplicatedValue.Add(item); + } + } + } + + + internal class DictionarySerializer : INetworkVariableSerializer> + where TKey : IEquatable + { + public void Write(FastBufferWriter writer, ref Dictionary value) + { + var isNull = value == null; + writer.WriteValueSafe(isNull); + if (!isNull) + { + writer.WriteValueSafe(value.Count); + foreach (var item in value) + { + var (key, val) = (item.Key, item.Value); + NetworkVariableSerialization.Write(writer, ref key); + NetworkVariableSerialization.Write(writer, ref val); + } + } + } + + public void Read(FastBufferReader reader, ref Dictionary value) + { + reader.ReadValueSafe(out bool isNull); + if (isNull) + { + value = null; + } + else + { + if (value == null) + { + value = new Dictionary(); + } + else + { + value.Clear(); + } + + reader.ReadValueSafe(out int len); + for (var i = 0; i < len; ++i) + { + (TKey key, TVal val) = (default, default); + NetworkVariableSerialization.Read(reader, ref key); + NetworkVariableSerialization.Read(reader, ref val); + value.Add(key, val); + } + } + } + + public void WriteDelta(FastBufferWriter writer, ref Dictionary value, ref Dictionary previousValue) + { + CollectionSerializationUtility.WriteDictionaryDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref Dictionary value) + { + CollectionSerializationUtility.ReadDictionaryDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out Dictionary value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in Dictionary value, ref Dictionary duplicatedValue) + { + if (duplicatedValue == null) + { + duplicatedValue = new Dictionary(); + } + + duplicatedValue.Clear(); + foreach (var item in value) + { + duplicatedValue.Add(item.Key, item.Value); + } + } + } + + internal class UnmanagedArraySerializer : INetworkVariableSerializer> where T : unmanaged + { + public void Write(FastBufferWriter writer, ref NativeArray value) + { + writer.WriteUnmanagedSafe(value); + } + + public void Read(FastBufferReader reader, ref NativeArray value) + { + value.Dispose(); + reader.ReadUnmanagedSafe(out value, Allocator.Persistent); + } + + public void WriteDelta(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) + { + CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeArray value) + { + CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) + { + reader.ReadUnmanagedSafe(out value, allocator); + } + + public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) + { + if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) + { + if (duplicatedValue.IsCreated) + { + duplicatedValue.Dispose(); + } + + duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); + } + + duplicatedValue.CopyFrom(value); + } + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + internal class UnmanagedListSerializer : INetworkVariableSerializer> where T : unmanaged + { + public void Write(FastBufferWriter writer, ref NativeList value) + { + writer.WriteUnmanagedSafe(value); + } + + public void Read(FastBufferReader reader, ref NativeList value) + { + reader.ReadUnmanagedSafeInPlace(ref value); + } + + public void WriteDelta(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) + { + CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeList value) + { + CollectionSerializationUtility.ReadNativeListDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeList value, ref NativeList duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeList(value.Length, Allocator.Persistent); + } + else if (value.Length != duplicatedValue.Length) + { + duplicatedValue.ResizeUninitialized(value.Length); + } + + duplicatedValue.CopyFrom(value); + } + } + + + internal class NativeHashSetSerializer : INetworkVariableSerializer> where T : unmanaged, IEquatable + { + public void Write(FastBufferWriter writer, ref NativeHashSet value) + { + writer.WriteValueSafe(value); + } + + public void Read(FastBufferReader reader, ref NativeHashSet value) + { + reader.ReadValueSafeInPlace(ref value); + } + + public void WriteDelta(FastBufferWriter writer, ref NativeHashSet value, ref NativeHashSet previousValue) + { + CollectionSerializationUtility.WriteNativeHashSetDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeHashSet value) + { + CollectionSerializationUtility.ReadNativeHashSetDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeHashSet value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeHashSet value, ref NativeHashSet duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeHashSet(value.Capacity, Allocator.Persistent); + } + + duplicatedValue.Clear(); + foreach (var item in value) + { + duplicatedValue.Add(item); + } + } + } + + + internal class NativeHashMapSerializer : INetworkVariableSerializer> + where TKey : unmanaged, IEquatable + where TVal : unmanaged + { + public void Write(FastBufferWriter writer, ref NativeHashMap value) + { + writer.WriteValueSafe(value); + } + + public void Read(FastBufferReader reader, ref NativeHashMap value) + { + reader.ReadValueSafeInPlace(ref value); + } + + public void WriteDelta(FastBufferWriter writer, ref NativeHashMap value, ref NativeHashMap previousValue) + { + CollectionSerializationUtility.WriteNativeHashMapDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeHashMap value) + { + CollectionSerializationUtility.ReadNativeHashMapDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeHashMap value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeHashMap value, ref NativeHashMap duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeHashMap(value.Capacity, Allocator.Persistent); + } + + duplicatedValue.Clear(); + foreach (var item in value) + { + duplicatedValue.Add(item.Key, item.Value); + } + } + } +#endif + + /// + /// Serializer for FixedStrings + /// + /// + internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged, INativeList, IUTF8Bytes + { + // The item type can only be bytes for fixedStrings, so the DA runtime doesn't need details on it + + public void Write(FastBufferWriter writer, ref T value) + { + writer.WriteValueSafe(value); + } + + public void Read(FastBufferReader reader, ref T value) + { + reader.ReadValueSafeInPlace(ref value); + } + + // Because of how strings are generally used, it is likely that most strings will still write as full strings + // instead of deltas. This actually adds one byte to the data to encode that it was serialized in full. + // But the potential savings from a small change to a large string are valuable enough to be worth that extra + // byte. + public unsafe void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) + { + using var changes = new ResizableBitVector(Allocator.Temp); + var minLength = math.min(value.Length, previousValue.Length); + var numChanges = 0; + for (var i = 0; i < minLength; ++i) + { + var val = value[i]; + var prevVal = previousValue[i]; + if (!NetworkVariableSerialization.AreEqual(ref val, ref prevVal)) + { + ++numChanges; + changes.Set(i); + } + } + + for (var i = previousValue.Length; i < value.Length; ++i) + { + ++numChanges; + changes.Set(i); + } + + if (changes.GetSerializedSize() + FastBufferWriter.GetWriteSize() * numChanges > FastBufferWriter.GetWriteSize() * value.Length) + { + // If there are too many changes, send the entire array. + writer.WriteByteSafe(1); // Flag that we're sending the entire array + writer.WriteValueSafe(value); + return; + } + + writer.WriteByte(0); // Flag that we're sending a delta + BytePacker.WriteValuePacked(writer, value.Length); + writer.WriteValueSafe(changes); + var ptr = value.GetUnsafePtr(); + var prevPtr = previousValue.GetUnsafePtr(); + for (var i = 0; i < value.Length; ++i) + { + if (changes.IsSet(i)) + { + if (i < previousValue.Length) + { + NetworkVariableSerialization.WriteDelta(writer, ref ptr[i], ref prevPtr[i]); + } + else + { + NetworkVariableSerialization.Write(writer, ref ptr[i]); + } + } + } + } + + public unsafe void ReadDelta(FastBufferReader reader, ref T value) + { + // Writing can use the NativeArray logic as it is, but reading is a little different. + // Using the NativeArray logic for reading would result in length changes allocating a new NativeArray, + // which is not what we want for FixedString. With FixedString, the actual size of the data does not change, + // only an in-memory "length" value - so if the length changes, the only thing we want to do is change + // that value, and otherwise read everything in-place. + reader.ReadByteSafe(out var full); + if (full == 1) + { + reader.ReadValueSafeInPlace(ref value); + return; + } + + ByteUnpacker.ReadValuePacked(reader, out int length); + var changes = new ResizableBitVector(Allocator.Temp); + using var toDispose = changes; + { + reader.ReadNetworkSerializableInPlace(ref changes); + + value.Length = length; + + var ptr = value.GetUnsafePtr(); + for (var i = 0; i < value.Length; ++i) + { + if (changes.IsSet(i)) + { + reader.ReadByte(out ptr[i]); + } + } + } + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + duplicatedValue = value; + } + } + + /// + /// Serializer for FixedStrings + /// + /// + internal class FixedStringArraySerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes + { + public void Write(FastBufferWriter writer, ref NativeArray value) + { + writer.WriteValueSafe(value); + } + + public void Read(FastBufferReader reader, ref NativeArray value) + { + value.Dispose(); + reader.ReadValueSafe(out value, Allocator.Persistent); + } + + + public void WriteDelta(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) + { + CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeArray value) + { + CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) + { + reader.ReadValueSafe(out value, allocator); + } + + public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) + { + if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) + { + if (duplicatedValue.IsCreated) + { + duplicatedValue.Dispose(); + } + + duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); + } + + duplicatedValue.CopyFrom(value); + } + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + /// + /// Serializer for FixedStrings + /// + /// + internal class FixedStringListSerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes + { + public void Write(FastBufferWriter writer, ref NativeList value) + { + writer.WriteValueSafe(value); + } + + public void Read(FastBufferReader reader, ref NativeList value) + { + reader.ReadValueSafeInPlace(ref value); + } + + public void WriteDelta(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) + { + CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeList value) + { + CollectionSerializationUtility.ReadNativeListDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeList value, ref NativeList duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeList(value.Length, Allocator.Persistent); + } + else if (value.Length != duplicatedValue.Length) + { + duplicatedValue.ResizeUninitialized(value.Length); + } + + duplicatedValue.CopyFrom(value); + } + } +#endif + + /// + /// Serializer for unmanaged INetworkSerializable types + /// + /// + internal class UnmanagedNetworkSerializableSerializer : INetworkVariableSerializer where T : unmanaged, INetworkSerializable + { + public void Write(FastBufferWriter writer, ref T value) + { + var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); + value.NetworkSerialize(bufferSerializer); + } + + public void Read(FastBufferReader reader, ref T value) + { + var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); + value.NetworkSerialize(bufferSerializer); + } + + public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) + { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); + return; + } + + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref T value) + { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.ReadDelta(reader, ref value); + return; + } + + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in T value, ref T duplicatedValue) + { + duplicatedValue = value; + } + } + + + /// + /// Serializer for unmanaged INetworkSerializable types + /// + /// + internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable + { + public void Write(FastBufferWriter writer, ref NativeArray value) + { + writer.WriteNetworkSerializable(value); + } + + public void Read(FastBufferReader reader, ref NativeArray value) + { + value.Dispose(); + reader.ReadNetworkSerializable(out value, Allocator.Persistent); + } + + + public void WriteDelta(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) + { + CollectionSerializationUtility.WriteNativeArrayDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeArray value) + { + CollectionSerializationUtility.ReadNativeArrayDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeArray value, Allocator allocator) + { + reader.ReadNetworkSerializable(out value, allocator); + } + + public void Duplicate(in NativeArray value, ref NativeArray duplicatedValue) + { + if (!duplicatedValue.IsCreated || duplicatedValue.Length != value.Length) + { + if (duplicatedValue.IsCreated) + { + duplicatedValue.Dispose(); + } + + duplicatedValue = new NativeArray(value.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); + } + + duplicatedValue.CopyFrom(value); + } + } + +#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + /// + /// Serializer for unmanaged INetworkSerializable types + /// + /// + internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable + { + public void Write(FastBufferWriter writer, ref NativeList value) + { + writer.WriteNetworkSerializable(value); + } + + public void Read(FastBufferReader reader, ref NativeList value) + { + reader.ReadNetworkSerializableInPlace(ref value); + } + + public void WriteDelta(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) + { + CollectionSerializationUtility.WriteNativeListDelta(writer, ref value, ref previousValue); + } + + public void ReadDelta(FastBufferReader reader, ref NativeList value) + { + CollectionSerializationUtility.ReadNativeListDelta(reader, ref value); + } + + void INetworkVariableSerializer>.ReadWithAllocator(FastBufferReader reader, out NativeList value, Allocator allocator) + { + throw new NotImplementedException(); + } + + public void Duplicate(in NativeList value, ref NativeList duplicatedValue) + { + if (!duplicatedValue.IsCreated) + { + duplicatedValue = new NativeList(value.Length, Allocator.Persistent); + } + else if (value.Length != duplicatedValue.Length) + { + duplicatedValue.ResizeUninitialized(value.Length); + } + + duplicatedValue.CopyFrom(value); + } + } +#endif + + /// + /// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it + /// has to be null-aware + /// + internal class ManagedNetworkSerializableSerializer : INetworkVariableSerializer where T : class, INetworkSerializable, new() + { + public void Write(FastBufferWriter writer, ref T value) + { + var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); + var isNull = value == null; + bufferSerializer.SerializeValue(ref isNull); + if (!isNull) + { + value.NetworkSerialize(bufferSerializer); + } + } + + public void Read(FastBufferReader reader, ref T value) + { + var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); + var isNull = false; + bufferSerializer.SerializeValue(ref isNull); + if (isNull) + { + value = null; + } + else + { + if (value == null) + { + value = new T(); + } + + value.NetworkSerialize(bufferSerializer); + } + } + + public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) + { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); + return; + } + + Write(writer, ref value); + } + + public void ReadDelta(FastBufferReader reader, ref T value) + { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.ReadDelta(reader, ref value); + return; + } + + Read(reader, ref value); + } + + void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, out T value, Allocator allocator) + { + throw new NotImplementedException(); + } + + + public void Duplicate(in T value, ref T duplicatedValue) + { + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); + var refValue = value; + Write(writer, ref refValue); + + using var reader = new FastBufferReader(writer, Allocator.None); + Read(reader, ref duplicatedValue); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs.meta new file mode 100644 index 0000000000..e430403d6d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bbfa170e9dd448bbbe381ce38d5c139d +timeCreated: 1718216671 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs new file mode 100644 index 0000000000..c39ca0f9a2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs @@ -0,0 +1,73 @@ +namespace Unity.Netcode +{ + /// + /// This class is used to register user serialization with NetworkVariables for types + /// that are serialized via user serialization, such as with FastBufferReader and FastBufferWriter + /// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows + /// users to tell NetworkVariable about those extension methods (or simply pass in a lambda) + /// + /// + public class UserNetworkVariableSerialization + { + /// + /// The write value delegate handler definition + /// + /// The to write the value of type `T` + /// The value of type `T` to be written + public delegate void WriteValueDelegate(FastBufferWriter writer, in T value); + + /// + /// The write value delegate handler definition + /// + /// The to write the value of type `T` + /// The value of type `T` to be written + public delegate void WriteDeltaDelegate(FastBufferWriter writer, in T value, in T previousValue); + + /// + /// The read value delegate handler definition + /// + /// The to read the value of type `T` + /// The value of type `T` to be read + public delegate void ReadValueDelegate(FastBufferReader reader, out T value); + + /// + /// The read value delegate handler definition + /// + /// The to read the value of type `T` + /// The value of type `T` to be read + public delegate void ReadDeltaDelegate(FastBufferReader reader, ref T value); + + /// + /// The read value delegate handler definition + /// + /// The to read the value of type `T` + /// The value of type `T` to be read + public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue); + + /// + /// Callback to write a value + /// + public static WriteValueDelegate WriteValue; + + /// + /// Callback to read a value + /// + public static ReadValueDelegate ReadValue; + + /// + /// Callback to write a delta between two values, based on computing the difference between the previous and + /// current values. + /// + public static WriteDeltaDelegate WriteDelta; + + /// + /// Callback to read a delta, applying only select changes to the current value. + /// + public static ReadDeltaDelegate ReadDelta; + + /// + /// Callback to create a duplicate of a value, used to check for dirty status. + /// + public static DuplicateValueDelegate DuplicateValue; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs.meta new file mode 100644 index 0000000000..a21252a911 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b295a6756640488b9824d2ec6e26ddae +timeCreated: 1718218272 \ No newline at end of file From 90cbb1e7410e093c9609211338bb09e090de4e45 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 14 Jun 2024 19:36:53 -0500 Subject: [PATCH 057/236] fix: ownership change halfprecision, session owner onclientconnected, and minor optimizations (#2948) * fix Fixing issue when using half float precision and ownership changes, the current base position needs to be synchronized. * fix This fixes the issue of not invoking OnClientConnected when scene management is enabled for the session owner of a distributed authority session. * update Minor reduction in allocs while processing received messages (with the asteroids stress test this removes a regular 83kb allocation that occurs when processing received messages). * update Making a client's owned objects be returned as an array as opposed to a list for performance purposes. * update Adding change log entries. * update Adding the PR number to the changelog entries. * fix Cast the local variable so message hooks based on type still work. * style adding and removing whitespaces... * style removing using statement for generic collections. * style Fixing spelling of private dictionary. * update Moving around how distributed authority mode is checked. First pass of getting UnityTransport to be able to dictate what network topology is currently available (during a session). * update minor updates to NetworkTransform * update Only display the Start Client option in the inspector view when distributed authority mode is selected. * updte Adding event for when a session owner is promoted. * update adding a few more change log entries. * test fix Adding the same changes made to UnityTransport to MockTransport. * update Additional control flow for topology usage check * fix Fixing issue with NetworkObject ownership properties not showing up in the inspector view. Renaming the version define from MULTIPLAYER_SDK_INSTALLED to MULTIPLAYER_SERVICES_SDK_INSTALLED. Removing the components folder that was used for the assembly that no longer exists. * update Modifying the EveryoneRpcTarget so that it uses the NotAuthorityRpcTarget and AuthorityRpcTarget when in distributed authority mode. * update handling minor merge conflict * fix Fixing issue with motion based on very small linear velocity values. * update Adding 2D rigibody threshold calculations. Disabling the auto session owner promotion in distributed authority host mode. * fix and style Initializing NetworkManager dependent properties within the OnNetworkSpawn of the NetworkRigidbodyBase class. Removing whitespaces from NetworkConnectionManager. * update adding change log entries * fix declaring var rotationThreshold when there is no physics package installed. * update reverting previous change. Did another deep profile with Unity 6 (5f1) and this area does not seem to be causing any allocations. * update Adding authority mode selection drop down and exposing the NetworkTransformEditor OnEnable method (making it virtual) so the control can be derived from and extended. * update adding changelog entry for NetworkTransformEditor adjustment. * update make sure to update to Owner authority during initialization. --- com.unity.netcode.gameobjects/CHANGELOG.md | 21 +++ com.unity.netcode.gameobjects/Components.meta | 8 -- .../Editor/NetworkManagerEditor.cs | 37 +++-- .../Editor/NetworkObjectEditor.cs | 4 +- .../Editor/NetworkTransformEditor.cs | 11 +- .../Editor/com.unity.netcode.editor.asmdef | 2 +- .../Components/NetworkRigidBodyBase.cs | 128 ++++++++++++++++++ .../Runtime/Components/NetworkTransform.cs | 95 +++++++++---- .../Runtime/Connection/NetworkClient.cs | 11 +- .../Connection/NetworkConnectionManager.cs | 3 + .../Runtime/Core/NetworkManager.cs | 43 ++++-- .../Runtime/Core/NetworkObject.cs | 2 +- .../Messages/ConnectionApprovedMessage.cs | 4 + .../Messages/NetworkTransformMessage.cs | 5 +- .../Messaging/NetworkMessageManager.cs | 18 ++- .../Messaging/RpcTargets/EveryoneRpcTarget.cs | 18 ++- .../NetworkVariableSerialization.cs | 2 + .../Runtime/Spawning/NetworkSpawnManager.cs | 10 +- .../Runtime/Transports/NetworkTransport.cs | 16 +++ .../Runtime/Transports/UTP/UnityTransport.cs | 5 + .../Runtime/com.unity.netcode.runtime.asmdef | 16 ++- .../TestHelpers/Runtime/MockTransport.cs | 5 + .../NetworkObjectOwnershipTests.cs | 6 +- 23 files changed, 379 insertions(+), 91 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Components.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f90cad4587..261b371684 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [Unreleased] + +### Added + +- Added event `NetworkManager.OnSessionOwnerPromoted` that is invoked when a new session owner promotion occurs. (#2948) +- Added `NetworkRigidBodyBase.GetLinearVelocity` and `NetworkRigidBodyBase.SetLinearVelocity` convenience/helper methods. (#2948) +- Added `NetworkRigidBodyBase.GetAngularVelocity` and `NetworkRigidBodyBase.SetAngularVelocity` convenience/helper methods. (#2948) + +### Fixed + +- Fixed issue when `NetworkTransform` half float precision is enabled and ownership changes the current base position was not being synchronized. (#2948) +- Fixed issue where `OnClientConnected` not being invoked on the session owner when connecting to a new distributed authority session. (#2948) +- Fixed issue where Rigidbody micro-motion (i.e. relatively small velocities) would result in non-authority instances slightly stuttering as the body would come to a rest (i.e. no motion). Now, the threshold value can increase at higher velocities and can decrease slightly below the provided threshold to account for this. (#2948) + +### Changed + +- Changed the client's owned objects is now returned (`NetworkClient` and `NetworkSpawnManager`) as an array as opposed to a list for performance purposes. (#2948) +- Changed `NetworkTransfrom.TryCommitTransformToServer` to be internal as it will be removed by the final 2.0.0 release. (#2948) +- Changed `NetworkTransformEditor.OnEnable` to a virtual method to be able to customize a `NetworkTransform` derived class by creating a derived editor control from `NetworkTransformEditor`. (#2948) + + ## [2.0.0-exp.5] - 2024-06-03 ### Added diff --git a/com.unity.netcode.gameobjects/Components.meta b/com.unity.netcode.gameobjects/Components.meta deleted file mode 100644 index d4eb0dae79..0000000000 --- a/com.unity.netcode.gameobjects/Components.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 8b267eb841a574dc083ac248a95d4443 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index f420592e6f..0db3a653f7 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -30,7 +30,7 @@ public class NetworkManagerEditor : UnityEditor.Editor private SerializedProperty m_ProtocolVersionProperty; private SerializedProperty m_NetworkTransportProperty; private SerializedProperty m_TickRateProperty; -#if MULTIPLAYER_SDK_INSTALLED +#if MULTIPLAYER_SERVICES_SDK_INSTALLED private SerializedProperty m_NetworkTopologyProperty; #endif private SerializedProperty m_ClientConnectionBufferTimeoutProperty; @@ -102,7 +102,7 @@ private void Initialize() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); -#if MULTIPLAYER_SDK_INSTALLED +#if MULTIPLAYER_SERVICES_SDK_INSTALLED m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology"); #endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); @@ -142,7 +142,7 @@ private void CheckNullProperties() m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); -#if MULTIPLAYER_SDK_INSTALLED +#if MULTIPLAYER_SERVICES_SDK_INSTALLED m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology"); #endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); @@ -186,7 +186,7 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel); -#if MULTIPLAYER_SDK_INSTALLED +#if MULTIPLAYER_SERVICES_SDK_INSTALLED EditorGUILayout.PropertyField(m_NetworkTopologyProperty); #endif EditorGUILayout.PropertyField(m_ProtocolVersionProperty); @@ -310,21 +310,32 @@ public override void OnInspectorGUI() GUI.enabled = false; } - if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix))) + if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) { - m_NetworkManager.StartHost(); - } + if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix))) + { + m_NetworkManager.StartHost(); + } - if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartServer(); - } + if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix))) + { + m_NetworkManager.StartServer(); + } - if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix))) + if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix))) + { + m_NetworkManager.StartClient(); + } + } + else { - m_NetworkManager.StartClient(); + if (GUILayout.Button(new GUIContent("Start Client", "Starts a distributed authority client instance" + buttonDisabledReasonSuffix))) + { + m_NetworkManager.StartClient(); + } } + if (!EditorApplication.isPlaying) { GUI.enabled = true; diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 9975482c28..ef6ea58c34 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -#if MULTIPLAYER_SDK_INSTALLED +#if BYPASS_DEFAULT_ENUM_DRAWER && MULTIPLAYER_SERVICES_SDK_INSTALLED using System.Linq; #endif using UnityEditor; @@ -148,7 +148,7 @@ private void OnDestroy() // Keeping this here just in case, but it appears that in Unity 6 the visual bugs with // enum flags is resolved -#if BYPASS_DEFAULT_ENUM_DRAWER && MULTIPLAYER_SDK_INSTALLED +#if BYPASS_DEFAULT_ENUM_DRAWER && MULTIPLAYER_SERVICES_SDK_INSTALLED [CustomPropertyDrawer(typeof(NetworkObject.OwnershipStatus))] public class NetworkObjectOwnership : PropertyDrawer { diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 4e7831d5d8..0dbab88145 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -30,6 +30,7 @@ public class NetworkTransformEditor : UnityEditor.Editor private SerializedProperty m_UseQuaternionCompression; private SerializedProperty m_UseHalfFloatPrecision; private SerializedProperty m_SlerpPosition; + private SerializedProperty m_AuthorityMode; private static int s_ToggleOffset = 45; private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5; @@ -38,7 +39,7 @@ public class NetworkTransformEditor : UnityEditor.Editor private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale"); /// - public void OnEnable() + public virtual void OnEnable() { m_UseUnreliableDeltas = serializedObject.FindProperty(nameof(NetworkTransform.UseUnreliableDeltas)); m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX)); @@ -59,12 +60,13 @@ public void OnEnable() m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression)); m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision)); m_SlerpPosition = serializedObject.FindProperty(nameof(NetworkTransform.SlerpPosition)); + m_AuthorityMode = serializedObject.FindProperty(nameof(NetworkTransform.AuthorityMode)); } /// public override void OnInspectorGUI() { - EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel); + EditorGUILayout.LabelField("Axis to Synchronize", EditorStyles.boldLabel); { GUILayout.BeginHorizontal(); @@ -126,6 +128,11 @@ public override void OnInspectorGUI() GUILayout.EndHorizontal(); } + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Authority", EditorStyles.boldLabel); + { + EditorGUILayout.PropertyField(m_AuthorityMode); + } EditorGUILayout.Space(); EditorGUILayout.LabelField("Thresholds", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_PositionThresholdProperty); diff --git a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef index fb85b90d0d..e80328cfad 100644 --- a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef +++ b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef @@ -57,7 +57,7 @@ { "name": "com.unity.services.multiplayer", "expression": "0.2.0", - "define": "MULTIPLAYER_SDK_INSTALLED" + "define": "MULTIPLAYER_SERVICES_SDK_INSTALLED" } ], "noEngineReferences": false diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index c2a24eb283..e0f37e2399 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -45,6 +45,9 @@ public abstract class NetworkRigidbodyBase : NetworkBehaviour private Rigidbody m_Rigidbody; private Rigidbody2D m_Rigidbody2D; internal NetworkTransform NetworkTransform; + private float m_TickFrequency; + private float m_TickRate; + private enum InterpolationTypes { None, @@ -120,6 +123,129 @@ protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform network } } + internal Vector3 GetAdjustedPositionThreshold() + { + // Since the threshold is a measurement of unity world space units per tick, we will allow for the maximum threshold + // to be no greater than the threshold measured in unity world space units per second + var thresholdMax = NetworkTransform.PositionThreshold * m_TickRate; + // Get the velocity in unity world space units per tick + var perTickVelocity = GetLinearVelocity() * m_TickFrequency; + // Since a rigid body can have "micro-motion" when allowed to come to rest (based on friction etc), we will allow for + // no less than 1/10th the threshold value. + var minThreshold = NetworkTransform.PositionThreshold * 0.1f; + + // Finally, we adjust the threshold based on the body's current velocity + perTickVelocity.x = Mathf.Clamp(Mathf.Abs(perTickVelocity.x), minThreshold, thresholdMax); + perTickVelocity.y = Mathf.Clamp(Mathf.Abs(perTickVelocity.y), minThreshold, thresholdMax); + // 2D Rigidbody only moves on x & y axis + if (!m_IsRigidbody2D) + { + perTickVelocity.z = Mathf.Clamp(Mathf.Abs(perTickVelocity.z), minThreshold, thresholdMax); + } + + return perTickVelocity; + } + + internal Vector3 GetAdjustedRotationThreshold() + { + // Since the rotation threshold is a measurement pf degrees per tick, we get the maximum threshold + // by calculating the threshold in degrees per second. + var thresholdMax = NetworkTransform.RotAngleThreshold * m_TickRate; + // Angular velocity is expressed in radians per second where as the rotation being checked is in degrees. + // Convert the angular velocity to degrees per second and then convert that to degrees per tick. + var rotationPerTick = (GetAngularVelocity() * Mathf.Rad2Deg) * m_TickFrequency; + var minThreshold = NetworkTransform.RotAngleThreshold * m_TickFrequency; + + // 2D Rigidbody only rotates around Z axis + if (!m_IsRigidbody2D) + { + rotationPerTick.x = Mathf.Clamp(Mathf.Abs(rotationPerTick.x), minThreshold, thresholdMax); + rotationPerTick.y = Mathf.Clamp(Mathf.Abs(rotationPerTick.y), minThreshold, thresholdMax); + } + rotationPerTick.z = Mathf.Clamp(Mathf.Abs(rotationPerTick.z), minThreshold, thresholdMax); + + return rotationPerTick; + } + + /// + /// Sets the linear velocity of the Rigidbody. + /// + /// + /// For , only the x and y components of the are applied. + /// + public void SetLinearVelocity(Vector3 linearVelocity) + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.velocity = linearVelocity; + } + else + { + m_Rigidbody.linearVelocity = linearVelocity; + } + } + + /// + /// Gets the linear velocity of the Rigidbody. + /// + /// + /// For , the velocity returned is only applied to the x and y components. + /// + /// as the linear velocity + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3 GetLinearVelocity() + { + if (m_IsRigidbody2D) + { + return m_Rigidbody2D.velocity; + } + else + { + return m_Rigidbody.linearVelocity; + } + } + + /// + /// Sets the angular velocity for the Rigidbody. + /// + /// + /// For , the z component of is only used to set the angular velocity. + /// A quick way to pass in a 2D angular velocity component is: * angularVelocity (where angularVelocity is a float) + /// + /// the angular velocity to apply to the body + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetAngularVelocity(Vector3 angularVelocity) + { + if (m_IsRigidbody2D) + { + m_Rigidbody2D.angularVelocity = angularVelocity.z; + } + else + { + m_Rigidbody.angularVelocity = angularVelocity; + } + } + + /// + /// Gets the angular velocity for the Rigidbody. + /// + /// + /// For , the z component of the returned is the angular velocity of the object. + /// + /// angular velocity as a + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3 GetAngularVelocity() + { + if (m_IsRigidbody2D) + { + return Vector3.forward * m_Rigidbody2D.velocity; + } + else + { + return m_Rigidbody.angularVelocity; + } + } + /// /// Gets the position of the Rigidbody /// @@ -501,6 +627,8 @@ internal void UpdateOwnershipAuthority() /// public override void OnNetworkSpawn() { + m_TickFrequency = 1.0f / NetworkManager.NetworkConfig.TickRate; + m_TickRate = NetworkManager.NetworkConfig.TickRate; UpdateOwnershipAuthority(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 5635194563..db3e9b83ae 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -920,6 +920,22 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endregion #region PROPERTIES AND GENERAL METHODS + + + public enum AuthorityModes + { + Server, + Owner, + } +#if MULTIPLAYER_SERVICES_SDK_INSTALLED + [Tooltip("Selects who has authority (sends state updates) over the transform. When the network topology is set to distributed authority, this always defaults to owner authority. If server (the default), then only server-side adjustments to the " + + "transform will be synchronized with clients. If owner (or client), then only the owner-side adjustments to the transform will be synchronized with both the server and other clients.")] +#else + [Tooltip("Selects who has authority (sends state updates) over the transform. If server (the default), then only server-side adjustments to the transform will be synchronized with clients. If owner (or client), " + + "then only the owner-side adjustments to the transform will be synchronized with both the server and other clients.")] +#endif + public AuthorityModes AuthorityMode; + /// /// The default position change threshold value. /// Any changes above this threshold will be replicated. @@ -1487,7 +1503,7 @@ protected override void OnSynchronize(ref BufferSerializer serializer) /// /// the transform to be committed /// time it was marked dirty - protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime) + internal void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime) { if (!IsSpawned) { @@ -1568,8 +1584,6 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz // If the transform has deltas (returns dirty) or if an explicitly set state is pending if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize)) { - m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OldState.LastSerializedSize; - // If the state was explicitly set, then update the network tick to match the locally calculate tick if (m_LocalAuthoritativeNetworkState.ExplicitSet) { @@ -1699,9 +1713,20 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra #if COM_UNITY_MODULES_PHYSICS var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; + + var positionThreshold = Vector3.one * PositionThreshold; + var rotationThreshold = Vector3.one * RotAngleThreshold; + + if (m_UseRigidbodyForMotion) + { + positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); + rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); + } #else var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; + var positionThreshold = Vector3.one * PositionThreshold; + var rotationThreshold = Vector3.one * RotAngleThreshold; #endif var rotAngles = rotation.eulerAngles; var scale = transformToUse.localScale; @@ -1828,21 +1853,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // Begin delta checks against last sent state update if (!UseHalfFloatPrecision) { - if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync)) { networkState.PositionX = position.x; networkState.HasPositionX = true; isPositionDirty = true; } - if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync)) { networkState.PositionY = position.y; networkState.HasPositionY = true; isPositionDirty = true; } - if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync)) { networkState.PositionZ = position.z; networkState.HasPositionZ = true; @@ -1863,7 +1888,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra { for (int i = 0; i < 3; i++) { - if (Math.Abs(position[i] - m_HalfPositionState.PreviousPosition[i]) >= PositionThreshold) + if (Math.Abs(position[i] - m_HalfPositionState.PreviousPosition[i]) >= positionThreshold[i]) { isPositionDirty = i == 0 ? SyncPositionX : i == 1 ? SyncPositionY : SyncPositionZ; if (!isPositionDirty) @@ -1958,21 +1983,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (!UseQuaternionSynchronization) { - if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync)) { networkState.RotAngleX = rotAngles.x; networkState.HasRotAngleX = true; isRotationDirty = true; } - if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync)) { networkState.RotAngleY = rotAngles.y; networkState.HasRotAngleY = true; isRotationDirty = true; } - if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync)) { networkState.RotAngleZ = rotAngles.z; networkState.HasRotAngleZ = true; @@ -1989,7 +2014,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var previousRotation = networkState.Rotation.eulerAngles; for (int i = 0; i < 3; i++) { - if (Mathf.Abs(Mathf.DeltaAngle(previousRotation[i], rotAngles[i])) >= RotAngleThreshold) + if (Mathf.Abs(Mathf.DeltaAngle(previousRotation[i], rotAngles[i])) >= rotationThreshold[i]) { isRotationDirty = true; break; @@ -2984,7 +3009,13 @@ private void InternalInitialization(bool isOwnershipChange = false) return; } m_CachedNetworkObject = NetworkObject; + if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) + { + AuthorityMode = AuthorityModes.Owner; + } CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner; + + var currentPosition = GetSpaceRelativePosition(); var currentRotation = GetSpaceRelativeRotation(); @@ -3011,22 +3042,24 @@ private void InternalInitialization(bool isOwnershipChange = false) #else var forUpdate = true; #endif + m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; if (CanCommitToTransform) { - // Make sure authority doesn't get added to updates (no need to do this on the authority side) m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, false); if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); + m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = isOwnershipChange; + SetState(teleportDisabled: false); } + m_CurrentPosition = currentPosition; m_TargetPosition = currentPosition; RegisterForTickUpdate(this); - m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; if (UseHalfFloatPrecision && isOwnershipChange && !IsServerAuthoritative() && Interpolate) { m_HalfFloatTargetTickOwnership = m_CachedNetworkManager.ServerTime.Tick; @@ -3038,16 +3071,13 @@ private void InternalInitialization(bool isOwnershipChange = false) m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, true); // Remove this instance from the tick update DeregisterForTickUpdate(this); - ResetInterpolatedStateToCurrentAuthoritativeState(); - m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; m_CurrentPosition = currentPosition; m_TargetPosition = currentPosition; m_CurrentScale = transform.localScale; m_TargetScale = transform.localScale; m_CurrentRotation = currentRotation; m_TargetRotation = currentRotation.eulerAngles; - } OnInitialize(ref m_LocalAuthoritativeNetworkState); } @@ -3296,7 +3326,7 @@ private void UpdateInterpolation() { var serverTime = m_CachedNetworkManager.ServerTime; var cachedServerTime = serverTime.Time; - var offset = (float)serverTime.TickOffset; + //var offset = (float)serverTime.TickOffset; #if COM_UNITY_MODULES_PHYSICS var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else @@ -3314,7 +3344,7 @@ private void UpdateInterpolation() //offset = m_NetworkTransformTickRegistration.Offset; //} - var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo, offset).Time; + var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) @@ -3388,28 +3418,32 @@ public virtual void OnFixedUpdate() #endif /// - /// Override this method and return false to switch to owner authoritative mode + /// Determines whether the is or based on the property. + /// You can override this method to control this logic. /// - /// ( or ) where when false it runs as owner-client authoritative + /// or protected virtual bool OnIsServerAuthoritative() { - if (m_CachedNetworkManager) - { - return !m_CachedNetworkManager.DistributedAuthorityMode; - } - return true; + return AuthorityMode == AuthorityModes.Server; } /// /// Method to determine if this instance is owner or server authoritative. /// /// - /// Used by to determines if this is server or owner authoritative. + /// When using a , this will always be viewed as a authoritative motion model. /// /// or public bool IsServerAuthoritative() { - return OnIsServerAuthoritative(); + if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) + { + return false; + } + else + { + return OnIsServerAuthoritative(); + } } #endregion @@ -3451,9 +3485,10 @@ internal void TransformStateUpdate(ulong senderId) private NetworkTransformMessage m_OutboundMessage = new NetworkTransformMessage(); - internal void SerializeMessage(FastBufferWriter writer, int targetVersion) + internal int SerializeMessage(FastBufferWriter writer, int targetVersion) { var networkObject = NetworkObject; + var position = writer.Position; BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, (int)NetworkBehaviourId); writer.WriteNetworkSerializable(m_LocalAuthoritativeNetworkState); @@ -3470,6 +3505,7 @@ internal void SerializeMessage(FastBufferWriter writer, int targetVersion) BytePacker.WriteValuePacked(writer, targetId); } } + return writer.Position - position; } /// @@ -3482,7 +3518,7 @@ private void UpdateTransformState() return; } - bool isServerAuthoritative = OnIsServerAuthoritative(); + bool isServerAuthoritative = IsServerAuthoritative(); if (isServerAuthoritative && !IsServer) { Debug.LogError($"Server authoritative {nameof(NetworkTransform)} can only be updated by the server!"); @@ -3526,6 +3562,7 @@ private void UpdateTransformState() // Clients (owner authoritative) send messages to the server-host NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); } + m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OutboundMessage.BytesWritten; } #endregion diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs index 8cee4e0068..ff6ed614e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs @@ -1,15 +1,8 @@ -using System.Collections.Generic; using UnityEngine; namespace Unity.Netcode { - public enum NetworkTopologyTypes - { - ClientServer, - DistributedAuthority - } - /// /// A NetworkClient /// @@ -61,9 +54,9 @@ public class NetworkClient public NetworkObject PlayerObject; /// - /// The list of NetworkObject's owned by this client instance + /// The NetworkObject's owned by this client instance /// - public List OwnedObjects => IsConnected ? SpawnManager.GetClientOwnedObjects(ClientId) : new List(); + public NetworkObject[] OwnedObjects => IsConnected ? SpawnManager.GetClientOwnedObjects(ClientId) : new NetworkObject[] { }; internal NetworkSpawnManager SpawnManager { get; private set; } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index cb19efe1d9..722fbb24b5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1229,6 +1229,8 @@ internal void OnClientDisconnectFromServer(ulong clientId) var message = new ClientDisconnectedMessage { ClientId = clientId }; MessageManager?.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); + // Used for testing/validation purposes only +#if ENABLE_DAHOST_AUTOPROMOTE_SESSION_OWNER if (NetworkManager.DistributedAuthorityMode && !NetworkManager.ShutdownInProgress && NetworkManager.IsListening) { var newSessionOwner = NetworkManager.LocalClientId; @@ -1259,6 +1261,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) MessageManager?.SendMessage(ref sessionOwnerMessage, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); NetworkManager.SetSessionOwner(newSessionOwner); } +#endif } // If the client ID transport map exists diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 340eced68c..3719ba7015 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -36,17 +36,13 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem #pragma warning restore IDE1006 // restore naming rule violation check + internal static bool IsDistributedAuthority; + /// /// Distributed Authority Mode /// Returns true if the current session is running in distributed authority mode. /// - public bool DistributedAuthorityMode - { - get - { - return NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority; - } - } + public bool DistributedAuthorityMode { get; private set; } /// /// Distributed Authority Mode @@ -131,6 +127,18 @@ public bool DAHost public ulong CurrentSessionOwner { get; internal set; } + /// + /// Delegate declaration for + /// + /// the new session owner client identifier + public delegate void OnSessionOwnerPromotedDelegateHandler(ulong sessionOwnerPromoted); + + /// + /// Network Topology: Distributed Authority + /// When a new session owner is promoted, this event is triggered on all connected clients + /// + public event OnSessionOwnerPromotedDelegateHandler OnSessionOwnerPromoted; + internal void SetSessionOwner(ulong sessionOwner) { var previousSessionOwner = CurrentSessionOwner; @@ -151,10 +159,12 @@ internal void SetSessionOwner(ulong sessionOwner) } } } + + OnSessionOwnerPromoted?.Invoke(sessionOwner); } // TODO: Make this internal after testing - public void PromoteSessionOwner(ulong clientId) + internal void PromoteSessionOwner(ulong clientId) { if (!DistributedAuthorityMode) { @@ -217,12 +227,27 @@ internal void NetworkTransformRegistration(NetworkTransform networkTransform, bo #endif } + private void UpdateTopology() + { + var transportTopology = IsListening ? NetworkConfig.NetworkTransport.CurrentTopology() : NetworkConfig.NetworkTopology; + if (transportTopology != NetworkConfig.NetworkTopology) + { + NetworkLog.LogErrorServer($"[Topology Mismatch] Transport detected an issue with the topology ({transportTopology} | {NetworkConfig.NetworkTopology}) usage or setting! Disconnecting from session."); + Shutdown(); + } + else + { + IsDistributedAuthority = DistributedAuthorityMode = transportTopology == NetworkTopologyTypes.DistributedAuthority; + } + } + public void NetworkUpdate(NetworkUpdateStage updateStage) { switch (updateStage) { case NetworkUpdateStage.EarlyUpdate: { + UpdateTopology(); ConnectionManager.ProcessPendingApprovals(); ConnectionManager.PollAndHandleNetworkEvents(); @@ -1012,6 +1037,8 @@ internal void Initialize(bool server) #endif NetworkTransformUpdate.Clear(); + UpdateTopology(); + //DANGOEXP TODO: Remove this before finalizing the experimental release NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 3e7ae26896..b927722a73 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -430,7 +430,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true) /// Determines whether a NetworkObject can be distributed to other clients during /// a session. /// -#if !MULTIPLAYER_SDK_INSTALLED +#if !MULTIPLAYER_SERVICES_SDK_INSTALLED [HideInInspector] #endif [SerializeField] diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 89eee0cb39..68b1572cce 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -251,6 +251,10 @@ public void Handle(ref NetworkContext context) // Synchronize the service with the initial session owner's loaded scenes and spawned objects networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); + + // When scene management is enabled and since the session owner is synchronizing the service (i.e. acting like host), + // we need to locallyh invoke the OnClientConnected callback at this point in time. + networkManager.ConnectionManager.InvokeOnClientConnectedCallback(OwnerClientId); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs index 3fbd869445..56f25f22c2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs @@ -17,6 +17,8 @@ internal struct NetworkTransformMessage : INetworkMessage internal NetworkTransform.NetworkTransformState State; private FastBufferReader m_CurrentReader; + internal int BytesWritten; + private unsafe void CopyPayload(ref FastBufferWriter writer) { writer.WriteBytesSafe(m_CurrentReader.GetUnsafePtrAtCurrentPosition(), m_CurrentReader.Length - m_CurrentReader.Position); @@ -30,7 +32,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } else { - NetworkTransform.SerializeMessage(writer, targetVersion); + BytesWritten = NetworkTransform.SerializeMessage(writer, targetVersion); } } @@ -75,6 +77,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer; reader.ReadNetworkSerializableInPlace(ref NetworkTransform.InboundState); + NetworkTransform.InboundState.LastSerializedSize = reader.Position - currentPosition; } else { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index b455c9bfa1..d42251e794 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -34,6 +34,9 @@ public InvalidMessageStructureException(string issue) : base(issue) internal class NetworkMessageManager : IDisposable { public bool StopProcessing = false; + private static Type s_ConnectionApprovedType = typeof(ConnectionApprovedMessage); + private static Type s_ConnectionRequestType = typeof(ConnectionRequestMessage); + private static Type s_DisconnectReasonType = typeof(DisconnectReasonMessage); private struct ReceiveQueueItem { @@ -524,6 +527,7 @@ internal void CleanupDisconnectedClients() return new T().Version; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = false) { if (!m_PerClientMessageVersions.TryGetValue(clientId, out var versionMap)) @@ -551,16 +555,20 @@ internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = fals return messageVersion; } + + public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, NetworkMessageManager manager) where T : INetworkMessage, new() { + var messageType = typeof(T); var message = new T(); var messageVersion = 0; + // Special cases because these are the messages that carry the version info - thus the version info isn't // populated yet when we get these. The first part of these messages always has to be the version data // and can't change. - if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage) && context.SenderId != manager.m_LocalClientId) + if (messageType != s_ConnectionRequestType && messageType != s_ConnectionApprovedType && messageType != s_DisconnectReasonType && context.SenderId != manager.m_LocalClientId) { - messageVersion = manager.GetMessageVersion(typeof(T), context.SenderId, true); + messageVersion = manager.GetMessageVersion(messageType, context.SenderId, true); if (messageVersion < 0) { return; @@ -612,7 +620,7 @@ internal int SendMessage(ref TMessageType messa var messageVersion = 0; // Special case because this is the message that carries the version info - thus the version info isn't populated yet when we get this. // The first part of this message always has to be the version data and can't change. - if (typeof(TMessageType) != typeof(ConnectionRequestMessage)) + if (typeof(TMessageType) != s_ConnectionRequestType) { messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]); if (messageVersion < 0) @@ -666,7 +674,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t // Special case because this is the message that carries the version info - thus the version info isn't populated yet when we get this. // The first part of this message always has to be the version data and can't change. - if (typeof(TMessageType) != typeof(ConnectionRequestMessage)) + if (typeof(TMessageType) != s_ConnectionRequestType) { var messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]); if (messageVersion < 0) @@ -746,7 +754,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t // Special case because this is the message that carries the version info - thus the version info isn't // populated yet when we get this. The first part of this message always has to be the version data // and can't change. - if (typeof(TMessageType) != typeof(ConnectionRequestMessage)) + if (typeof(TMessageType) != s_ConnectionRequestType) { messageVersion = GetMessageVersion(typeof(TMessageType), clientId); if (messageVersion < 0) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs index b0ae7382c7..7b2089f6b8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs @@ -4,23 +4,37 @@ internal class EveryoneRpcTarget : BaseRpcTarget { private NotServerRpcTarget m_NotServerRpcTarget; private ServerRpcTarget m_ServerRpcTarget; + private NotAuthorityRpcTarget m_NotAuthorityRpcTarget; + private AuthorityRpcTarget m_AuthorityRpcTarget; public override void Dispose() { m_NotServerRpcTarget.Dispose(); m_ServerRpcTarget.Dispose(); + m_NotAuthorityRpcTarget.Dispose(); + m_AuthorityRpcTarget.Dispose(); } internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { - m_NotServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); - m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + if (NetworkManager.IsDistributedAuthority) + { + m_NotAuthorityRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + m_AuthorityRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } + else + { + m_NotServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } } internal EveryoneRpcTarget(NetworkManager manager) : base(manager) { m_NotServerRpcTarget = new NotServerRpcTarget(manager); m_ServerRpcTarget = new ServerRpcTarget(manager); + m_NotAuthorityRpcTarget = new NotAuthorityRpcTarget(manager); + m_AuthorityRpcTarget = new AuthorityRpcTarget(manager); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs index 683266050b..ebaba37a6e 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -14,6 +14,8 @@ public static class NetworkVariableSerialization { internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); + internal static bool IsDistributedAuthority => NetworkManager.IsDistributedAuthority; + /// /// The collection item type tells the CMB server how to read the bytes of each item in the collection /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index e021505504..0de0da4fdd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -275,17 +275,17 @@ internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, } /// - /// Returns a list of all NetworkObjects that belong to a client. + /// Returns an array of all NetworkObjects that belong to a client. /// - /// the client's id - /// returns the list of s owned by the client - public List GetClientOwnedObjects(ulong clientId) + /// the client's id + /// returns an array of the s owned by the client + public NetworkObject[] GetClientOwnedObjects(ulong clientId) { if (!OwnershipToObjectsTable.ContainsKey(clientId)) { OwnershipToObjectsTable.Add(clientId, new Dictionary()); } - return OwnershipToObjectsTable[clientId].Values.ToList(); + return OwnershipToObjectsTable[clientId].Values.ToArray(); } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs index 9de70aff65..b624120269 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs @@ -106,6 +106,22 @@ protected void InvokeOnTransportEvent(NetworkEvent eventType, ulong clientId, Ar /// /// /// optionally pass in NetworkManager public abstract void Initialize(NetworkManager networkManager = null); + + protected virtual NetworkTopologyTypes OnCurrentTopology() + { + return NetworkTopologyTypes.ClientServer; + } + + internal NetworkTopologyTypes CurrentTopology() + { + return OnCurrentTopology(); + } + } + + public enum NetworkTopologyTypes + { + ClientServer, + DistributedAuthority } #if UNITY_INCLUDE_TESTS diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 6922699ddd..f3d036b751 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1477,6 +1477,11 @@ private void ConfigureSimulatorForUtp1() } #endif + protected override NetworkTopologyTypes OnCurrentTopology() + { + return NetworkManager != null ? NetworkManager.NetworkConfig.NetworkTopology : NetworkTopologyTypes.ClientServer; + } + private string m_ServerPrivateKey; private string m_ServerCertificate; diff --git a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef index 1577ff03f9..b76ae6f223 100644 --- a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef +++ b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef @@ -15,7 +15,13 @@ "Unity.Burst", "Unity.Mathematics" ], + "includePlatforms": [], + "excludePlatforms": [], "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], "versionDefines": [ { "name": "com.unity.multiplayer.tools", @@ -61,6 +67,12 @@ "name": "com.unity.modules.physics2d", "expression": "", "define": "COM_UNITY_MODULES_PHYSICS2D" + }, + { + "name": "com.unity.services.multiplayer", + "expression": "0.2.0", + "define": "MULTIPLAYER_SERVICES_SDK_INSTALLED" } - ] -} + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs index 1bfc130781..4ff644e66c 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs @@ -81,6 +81,11 @@ public override void Shutdown() { } + protected override NetworkTopologyTypes OnCurrentTopology() + { + return NetworkManager != null ? NetworkManager.NetworkConfig.NetworkTopology : NetworkTopologyTypes.ClientServer; + } + public override void Initialize(NetworkManager networkManager = null) { NetworkManager = networkManager; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index f1f6e53d53..faf4402a13 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -358,7 +358,7 @@ private bool AllClientsHaveCorrectObjectCount() foreach (var clientNetworkManager in m_ClientNetworkManagers) { - if (clientNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects) + if (clientNetworkManager.LocalClient.OwnedObjects.Length < k_NumberOfSpawnedObjects) { return false; } @@ -372,7 +372,7 @@ private bool ServerHasCorrectClientOwnedObjectCount() // Only check when we are the host if (m_ServerNetworkManager.IsHost) { - if (m_ServerNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects) + if (m_ServerNetworkManager.LocalClient.OwnedObjects.Length < k_NumberOfSpawnedObjects) { return false; } @@ -380,7 +380,7 @@ private bool ServerHasCorrectClientOwnedObjectCount() foreach (var connectedClient in m_ServerNetworkManager.ConnectedClients) { - if (connectedClient.Value.OwnedObjects.Count < k_NumberOfSpawnedObjects) + if (connectedClient.Value.OwnedObjects.Length < k_NumberOfSpawnedObjects) { return false; } From 2493ff603bd79a8318130ff1543a2a65eb1225d1 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 15 Jun 2024 19:58:03 -0500 Subject: [PATCH 058/236] fix Send InSceneNetworkObjectsSpawned and InternalNetworkSessionSynchronized NetworkBehaviour notifications on the server, host, and session owner side when first starting a session. --- .../Runtime/Core/NetworkManager.cs | 22 +++++++++++++++++++ .../Messages/ConnectionApprovedMessage.cs | 14 ++++++++++++ 2 files changed, 36 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 3719ba7015..b956cfa2c0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1218,6 +1218,17 @@ public bool StartServer() { SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + // Notify the server that all in-scnee placed NetworkObjects are spawned at this time. + foreach (var networkObject in SpawnManager.SpawnedObjectsList) + { + networkObject.InternalInSceneNetworkObjectsSpawned(); + } + + // Notify the server that everything should be synchronized/spawned at this time. + foreach (var networkObject in SpawnManager.SpawnedObjectsList) + { + networkObject.InternalNetworkSessionSynchronized(); + } OnServerStarted?.Invoke(); ConnectionManager.LocalClient.IsApproved = true; return true; @@ -1364,6 +1375,17 @@ private void HostServerInitialize() } SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + // Notify the host that all in-scnee placed NetworkObjects are spawned at this time. + foreach (var networkObject in SpawnManager.SpawnedObjectsList) + { + networkObject.InternalInSceneNetworkObjectsSpawned(); + } + + // Notify the host that everything should be synchronized/spawned at this time. + foreach (var networkObject in SpawnManager.SpawnedObjectsList) + { + networkObject.InternalNetworkSessionSynchronized(); + } OnServerStarted?.Invoke(); OnClientStarted?.Invoke(); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 68b1572cce..82eef2489d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -243,6 +243,13 @@ public void Handle(ref NetworkContext context) // Spawn any in-scene placed NetworkObjects networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); + // With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself, + // we need to notify the session owner that all in-scnee placed NetworkObjects are spawned at this time. + foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) + { + networkObject.InternalInSceneNetworkObjectsSpawned(); + } + // Spawn the local player of the session owner if (networkManager.AutoSpawnPlayerPrefabClientSide) { @@ -252,6 +259,13 @@ public void Handle(ref NetworkContext context) // Synchronize the service with the initial session owner's loaded scenes and spawned objects networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); + // With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself, + // we need to notify the session owner that everything should be synchronized/spawned at this time. + foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) + { + networkObject.InternalNetworkSessionSynchronized(); + } + // When scene management is enabled and since the session owner is synchronizing the service (i.e. acting like host), // we need to locallyh invoke the OnClientConnected callback at this point in time. networkManager.ConnectionManager.InvokeOnClientConnectedCallback(OwnerClientId); From 59cdd30c51e98805860492fcaadbcde8bec85fab Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 18 Jun 2024 16:40:56 -0400 Subject: [PATCH 059/236] fix: MPSNGM-302 Fix DA integration tests (#2955) --- .../DistributedAuthority/DistributedAuthorityCodecTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 390455a761..843dfd364e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -87,7 +87,7 @@ protected override void OnServerAndClientsCreated() Client.LogLevel = LogLevel.Developer; // Validate we are in distributed authority mode with client side spawning and using CMB Service - Assert.True(Client.DistributedAuthorityMode, "Distributed authority is not set!"); + Assert.True(Client.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, "Distributed authority topology is not set!"); Assert.True(Client.AutoSpawnPlayerPrefabClientSide, "Client side spawning is not set!"); Assert.True(Client.CMBServiceConnection, "CMBServiceConnection is not set!"); @@ -101,6 +101,9 @@ protected override void OnServerAndClientsCreated() protected override IEnumerator OnStartedServerAndClients() { + // Validate the NetworkManager are in distributed authority mode + Assert.True(Client.DistributedAuthorityMode, "Distributed authority is not set!"); + // Register hooks after starting clients and server (in this case just the one client) // We do this at this point in time because the MessageManager exists (happens within the same call stack when starting NetworkManagers) m_ClientCodecHook = new CodecTestHooks(); From 8b13c4eb3722dcb56c7ca43ae151a3d34f549be8 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 26 Jun 2024 12:05:58 -0500 Subject: [PATCH 060/236] chore: merge 2.0.0 pre.2 into develop 2.0.0 (#2965) * chore: updating changelog and package.json (#2952) * updating changelog and package.json * adding date * fix: rotation not being 1.0 issue using rigid body for motion (#2954) * fix this fixes the issue where a quaternion could potentially be close to 1.0 but off by a very very very small amount. Under this scenario, we Normalize the quaternion prior to invoking MoveRotation. * feat: up-port of network variable traits, anticipation, serialized null references, and removal of animator component requirement (#2957) * update This contains the updates for PR-2820 (Anticipated NetworkVariable and NetworkTransform) with minor adjustments based on updates in the v2.0.0 branch. Updating the package version. Adding updated changelog entries. up-port of PR-2872 up-port of PR-2874. updating change log file --------- Co-authored-by: Frank Luong <100299641+fluong6@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 11 +- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 1 + .../Editor/NetworkTransformEditor.cs | 6 +- .../Components/AnticipatedNetworkTransform.cs | 497 +++++++++++++++++ .../AnticipatedNetworkTransform.cs.meta | 2 + .../Runtime/Components/Messages.meta | 8 - .../Runtime/Components/NetworkAnimator.cs | 1 - .../Components/NetworkRigidBodyBase.cs | 14 + .../Runtime/Components/NetworkTransform.cs | 24 +- .../Runtime/Core/NetworkBehaviour.cs | 43 +- .../Runtime/Core/NetworkBehaviourUpdater.cs | 6 +- .../Runtime/Core/NetworkManager.cs | 32 ++ .../Runtime/Core/NetworkUpdateLoop.cs | 20 + .../Runtime/Messaging/ILPPMessageProvider.cs | 6 +- .../AnticipationCounterSyncPingMessage.cs | 70 +++ ...AnticipationCounterSyncPingMessage.cs.meta | 2 + .../Messages/NetworkVariableDeltaMessage.cs | 3 +- .../AnticipatedNetworkVariable.cs | 392 +++++++++++++ .../AnticipatedNetworkVariable.cs.meta | 2 + .../NetworkVariable/NetworkVariable.cs | 23 + .../NetworkVariable/NetworkVariableBase.cs | 80 +++ .../NetworkBehaviourReference.cs | 11 +- .../Serialization/NetworkObjectReference.cs | 20 +- .../Runtime/Timing/AnticipationSystem.cs | 100 ++++ .../Runtime/Timing/AnticipationSystem.cs.meta | 2 + .../Runtime/Timing/NetworkTime.cs | 5 + .../Runtime/Timing/NetworkTimeSystem.cs | 4 +- .../TestHelpers/Runtime/MockTransport.cs | 43 +- .../Runtime/NetcodeIntegrationTest.cs | 64 ++- .../Runtime/NetworkManagerEventsTests.cs | 17 +- .../NetworkTransformAnticipationTests.cs | 521 ++++++++++++++++++ .../NetworkTransformAnticipationTests.cs.meta | 2 + .../NetworkVariableAnticipationTests.cs | 420 ++++++++++++++ .../NetworkVariableAnticipationTests.cs.meta | 2 + .../Tests/Runtime/NetworkVariableTests.cs | 9 +- .../NetworkVariableTestsHelperTypes.cs | 2 +- .../Runtime/NetworkVariableTraitsTests.cs | 138 +++++ .../NetworkVariableTraitsTests.cs.meta | 2 + ...tworkVariableUserSerializableTypesTests.cs | 42 ++ .../NetworkBehaviourReferenceTests.cs | 47 +- .../NetworkObjectReferenceTests.cs | 75 ++- com.unity.netcode.gameobjects/package.json | 2 +- 42 files changed, 2704 insertions(+), 67 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs.meta delete mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Messages.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 261b371684..f21849e0fe 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,10 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] +## [2.0.0-pre.2] - 2024-06-17 ### Added +- Added `AnticipatedNetworkVariable`, which adds support for client anticipation of `NetworkVariable` values, allowing for more responsive gameplay. (#2957) +- Added `AnticipatedNetworkTransform`, which adds support for client anticipation of NetworkTransforms. (#2957) +- Added `NetworkVariableBase.ExceedsDirtinessThreshold` to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in `NetworkVariable` with the callback `NetworkVariable.CheckExceedsDirtinessThreshold`). (#2957) +- Added `NetworkVariableUpdateTraits`, which add additional throttling support: `MinSecondsBetweenUpdates` will prevent the `NetworkVariable` from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while `MaxSecondsBetweenUpdates` will force a dirty `NetworkVariable` to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2957) +- Added virtual method `NetworkVariableBase.OnInitialize` which can be used by `NetworkVariable` subclasses to add initialization code. (#2957) +- Added `NetworkTime.TickWithPartial`, which represents the current tick as a double that includes the fractional/partial tick value. (#2957) +- Added `NetworkTickSystem.AnticipationTick`, which can be helpful with implementation of client anticipation. This value represents the tick the current local client was at at the beginning of the most recent network round trip, which enables it to correlate server update ticks with the client tick that may have triggered them. (#2957) - Added event `NetworkManager.OnSessionOwnerPromoted` that is invoked when a new session owner promotion occurs. (#2948) - Added `NetworkRigidBodyBase.GetLinearVelocity` and `NetworkRigidBodyBase.SetLinearVelocity` convenience/helper methods. (#2948) - Added `NetworkRigidBodyBase.GetAngularVelocity` and `NetworkRigidBodyBase.SetAngularVelocity` convenience/helper methods. (#2948) @@ -22,6 +29,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2957) +- Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2957) - Changed the client's owned objects is now returned (`NetworkClient` and `NetworkSpawnManager`) as an array as opposed to a list for performance purposes. (#2948) - Changed `NetworkTransfrom.TryCommitTransformToServer` to be internal as it will be removed by the final 2.0.0 release. (#2948) - Changed `NetworkTransformEditor.OnEnable` to a virtual method to be able to customize a `NetworkTransform` derived class by creating a derived editor control from `NetworkTransformEditor`. (#2948) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index fb583e2cdd..e67ee0ef81 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -409,6 +409,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, } else { + m_Diagnostics.AddError($"{type}: Managed type in NetworkVariable must implement IEquatable<{type}>"); equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef); } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 0dbab88145..4affff1ffb 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -66,6 +66,7 @@ public virtual void OnEnable() /// public override void OnInspectorGUI() { + var networkTransform = target as NetworkTransform; EditorGUILayout.LabelField("Axis to Synchronize", EditorStyles.boldLabel); { GUILayout.BeginHorizontal(); @@ -144,7 +145,10 @@ public override void OnInspectorGUI() EditorGUILayout.Space(); EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_InLocalSpaceProperty); - EditorGUILayout.PropertyField(m_InterpolateProperty); + if (!networkTransform.HideInterpolateValue) + { + EditorGUILayout.PropertyField(m_InterpolateProperty); + } EditorGUILayout.PropertyField(m_SlerpPosition); EditorGUILayout.PropertyField(m_UseQuaternionSynchronization); if (m_UseQuaternionSynchronization.boolValue) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs new file mode 100644 index 0000000000..e8ae5890bf --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -0,0 +1,497 @@ +using Unity.Mathematics; +using UnityEngine; + +namespace Unity.Netcode.Components +{ + +#pragma warning disable IDE0001 + /// + /// A subclass of that supports basic client anticipation - the client + /// can set a value on the belief that the server will update it to reflect the same value in a future update + /// (i.e., as the result of an RPC call). This value can then be adjusted as new updates from the server come in, + /// in three basic modes: + /// + /// + /// + /// Snap: In this mode (with set to + /// and no callback), + /// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value, + /// resulting in a "snap" to the new value if it is different from the anticipated value. + /// + /// Smooth: In this mode (with set to + /// and an callback that calls + /// from the anticipated value to the authority value with an appropriate + /// -style smooth function), when a more up-to-date value is received from the authority, + /// it will interpolate over time from an incorrect anticipated value to the correct authoritative value. + /// + /// Constant Reanticipation: In this mode (with set to + /// and an that calculates a + /// new anticipated value based on the current authoritative value), when a more up-to-date value is received from + /// the authority, user code calculates a new anticipated value, possibly calling to interpolate + /// between the previous anticipation and the new anticipation. This is useful for values that change frequently and + /// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply + /// need a one-time anticipation when the user performs that action. + /// + /// + /// + /// Note that these three modes may be combined. For example, if an callback + /// does not call either or one of the Anticipate methods, the result will be a snap to the + /// authoritative value, enabling for a callback that may conditionally call when the + /// difference between the anticipated and authoritative values is within some threshold, but fall back to + /// snap behavior if the difference is too large. + /// +#pragma warning restore IDE0001 + [DisallowMultipleComponent] + [AddComponentMenu("Netcode/Anticipated Network Transform")] + public class AnticipatedNetworkTransform : NetworkTransform + { + +#if UNITY_EDITOR + internal override bool HideInterpolateValue => true; +#endif + + public struct TransformState + { + public Vector3 Position; + public Quaternion Rotation; + public Vector3 Scale; + } + + private TransformState m_AuthoritativeTransform = new TransformState(); + private TransformState m_AnticipatedTransform = new TransformState(); + private TransformState m_PreviousAnticipatedTransform = new TransformState(); + private ulong m_LastAnticipaionCounter; + private ulong m_LastAuthorityUpdateCounter; + + private TransformState m_SmoothFrom; + private TransformState m_SmoothTo; + private float m_SmoothDuration; + private float m_CurrentSmoothTime; + + private bool m_OutstandingAuthorityChange = false; + +#if UNITY_EDITOR + private void Reset() + { + // Anticipation + smoothing is a form of interpolation, and adding NetworkTransform's buffered interpolation + // makes the anticipation get weird, so we default it to false. + Interpolate = false; + } +#endif + +#pragma warning disable IDE0001 + /// + /// Defines what the behavior should be if we receive a value from the server with an earlier associated + /// time value than the anticipation time value. + ///

+ /// If this is , the stale data will be ignored and the authoritative + /// value will not replace the anticipated value until the anticipation time is reached. + /// and will also not be invoked for this stale data. + ///

+ /// If this is , the stale data will replace the anticipated data and + /// and will be invoked. + /// In this case, the authoritativeTime value passed to will be lower than + /// the anticipationTime value, and that callback can be used to calculate a new anticipated value. + ///
+#pragma warning restore IDE0001 + public StaleDataHandling StaleDataHandling = StaleDataHandling.Reanticipate; + + /// + /// Contains the current state of this transform on the server side. + /// Note that, on the server side, this gets updated at the end of the frame, and will not immediately reflect + /// changes to the transform. + /// + public TransformState AuthoritativeState => m_AuthoritativeTransform; + + /// + /// Contains the current anticipated state, which will match the values of this object's + /// actual . When a server + /// update arrives, this value will be overwritten by the new + /// server value (unless stale data handling is set to "Ignore" + /// and the update is determined to be stale). This value will + /// be duplicated in , which + /// will NOT be overwritten in server updates. + /// + public TransformState AnticipatedState => m_AnticipatedTransform; + + /// + /// Indicates whether this transform currently needs + /// reanticipation. If this is true, the anticipated value + /// has been overwritten by the authoritative value from the + /// server; the previous anticipated value is stored in + /// + public bool ShouldReanticipate + { + get; + private set; + } + + /// + /// Holds the most recent anticipated state, whatever was + /// most recently set using the Anticipate methods. Unlike + /// , this does not get overwritten + /// when a server update arrives. + /// + public TransformState PreviousAnticipatedState => m_PreviousAnticipatedTransform; + + /// + /// Anticipate that, at the end of one round trip to the server, this transform will be in the given + /// + /// + /// + public void AnticipateMove(Vector3 newPosition) + { + if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) + { + return; + } + transform.position = newPosition; + m_AnticipatedTransform.Position = newPosition; + if (CanCommitToTransform) + { + m_AuthoritativeTransform.Position = newPosition; + } + + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + + m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter; + + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + } + + /// + /// Anticipate that, at the end of one round trip to the server, this transform will have the given + /// + /// + /// + public void AnticipateRotate(Quaternion newRotation) + { + if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) + { + return; + } + transform.rotation = newRotation; + m_AnticipatedTransform.Rotation = newRotation; + if (CanCommitToTransform) + { + m_AuthoritativeTransform.Rotation = newRotation; + } + + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + + m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter; + + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + } + + /// + /// Anticipate that, at the end of one round trip to the server, this transform will have the given + /// + /// + /// + public void AnticipateScale(Vector3 newScale) + { + if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) + { + return; + } + transform.localScale = newScale; + m_AnticipatedTransform.Scale = newScale; + if (CanCommitToTransform) + { + m_AuthoritativeTransform.Scale = newScale; + } + + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + + m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter; + + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + } + + /// + /// Anticipate that, at the end of one round trip to the server, the transform will have the given + /// + /// + /// + public void AnticipateState(TransformState newState) + { + if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) + { + return; + } + var transform_ = transform; + transform_.position = newState.Position; + transform_.rotation = newState.Rotation; + transform_.localScale = newState.Scale; + m_AnticipatedTransform = newState; + if (CanCommitToTransform) + { + m_AuthoritativeTransform = newState; + } + + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + } + + public override void OnUpdate() + { + // If not spawned or this instance has authority, exit early + if (!IsSpawned) + { + return; + } + // Do not call the base class implementation... + // AnticipatedNetworkTransform applies its authoritative state immediately rather than waiting for update + // This is because AnticipatedNetworkTransforms may need to reference each other in reanticipating + // and we will want all reanticipation done before anything else wants to reference the transform in + // OnUpdate() + //base.Update(); + + if (m_CurrentSmoothTime < m_SmoothDuration) + { + m_CurrentSmoothTime += NetworkManager.RealTimeProvider.DeltaTime; + var transform_ = transform; + var pct = math.min(m_CurrentSmoothTime / m_SmoothDuration, 1f); + + m_AnticipatedTransform = new TransformState + { + Position = Vector3.Lerp(m_SmoothFrom.Position, m_SmoothTo.Position, pct), + Rotation = Quaternion.Slerp(m_SmoothFrom.Rotation, m_SmoothTo.Rotation, pct), + Scale = Vector3.Lerp(m_SmoothFrom.Scale, m_SmoothTo.Scale, pct) + }; + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + if (!CanCommitToTransform) + { + transform_.position = m_AnticipatedTransform.Position; + transform_.localScale = m_AnticipatedTransform.Scale; + transform_.rotation = m_AnticipatedTransform.Rotation; + } + } + } + + internal class AnticipatedObject : IAnticipationEventReceiver, IAnticipatedObject + { + public AnticipatedNetworkTransform Transform; + + + public void SetupForRender() + { + if (Transform.CanCommitToTransform) + { + var transform_ = Transform.transform; + Transform.m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + if (Transform.m_CurrentSmoothTime >= Transform.m_SmoothDuration) + { + // If we've had a call to Smooth() we'll continue interpolating. + // Otherwise we'll go ahead and make the visual and actual locations + // match. + Transform.m_AnticipatedTransform = Transform.m_AuthoritativeTransform; + } + + transform_.position = Transform.m_AnticipatedTransform.Position; + transform_.rotation = Transform.m_AnticipatedTransform.Rotation; + transform_.localScale = Transform.m_AnticipatedTransform.Scale; + } + } + + public void SetupForUpdate() + { + if (Transform.CanCommitToTransform) + { + var transform_ = Transform.transform; + transform_.position = Transform.m_AuthoritativeTransform.Position; + transform_.rotation = Transform.m_AuthoritativeTransform.Rotation; + transform_.localScale = Transform.m_AuthoritativeTransform.Scale; + } + } + + public void Update() + { + // No need to do this, it's handled by NetworkTransform.OnUpdate + } + + public void ResetAnticipation() + { + Transform.ShouldReanticipate = false; + } + + public NetworkObject OwnerObject => Transform.NetworkObject; + } + + private AnticipatedObject m_AnticipatedObject = null; + + private void ResetAnticipatedState() + { + var transform_ = transform; + m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + m_AnticipatedTransform = m_AuthoritativeTransform; + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + } + + protected override void OnSynchronize(ref BufferSerializer serializer) + { + base.OnSynchronize(ref serializer); + if (!CanCommitToTransform) + { + m_OutstandingAuthorityChange = true; + ApplyAuthoritativeState(); + ResetAnticipatedState(); + } + } + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + m_OutstandingAuthorityChange = true; + ApplyAuthoritativeState(); + ResetAnticipatedState(); + + m_AnticipatedObject = new AnticipatedObject { Transform = this }; + NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); + NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); + } + + public override void OnNetworkDespawn() + { + if (m_AnticipatedObject != null) + { + NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject); + NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); + NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); + m_AnticipatedObject = null; + } + ResetAnticipatedState(); + + base.OnNetworkDespawn(); + } + + public override void OnDestroy() + { + if (m_AnticipatedObject != null) + { + NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject); + NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); + NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); + m_AnticipatedObject = null; + } + + base.OnDestroy(); + } + + /// + /// Interpolate between the transform represented by to the transform represented by + /// over of real time. The duration uses + /// , so it is affected by . + /// + /// + /// + /// + public void Smooth(TransformState from, TransformState to, float durationSeconds) + { + var transform_ = transform; + if (durationSeconds <= 0) + { + m_AnticipatedTransform = to; + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + transform_.position = to.Position; + transform_.rotation = to.Rotation; + transform_.localScale = to.Scale; + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + return; + } + m_AnticipatedTransform = from; + m_PreviousAnticipatedTransform = m_AnticipatedTransform; + + if (!CanCommitToTransform) + { + transform_.position = from.Position; + transform_.rotation = from.Rotation; + transform_.localScale = from.Scale; + } + + m_SmoothFrom = from; + m_SmoothTo = to; + m_SmoothDuration = durationSeconds; + m_CurrentSmoothTime = 0; + } + + protected override void OnBeforeUpdateTransformState() + { + // this is called when new data comes from the server + m_LastAuthorityUpdateCounter = NetworkManager.AnticipationSystem.LastAnticipationAck; + m_OutstandingAuthorityChange = true; + } + + protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState) + { + base.OnNetworkTransformStateUpdated(ref oldState, ref newState); + ApplyAuthoritativeState(); + } + + protected override void OnTransformUpdated() + { + if (CanCommitToTransform || m_AnticipatedObject == null) + { + return; + } + // this is called pretty much every frame and will change the transform + // If we've overridden the transform with an anticipated state, we need to be able to change it back + // to the anticipated state (while updating the authority state accordingly) or else + // mark this transform for reanticipation + var transform_ = transform; + + var previousAnticipatedTransform = m_AnticipatedTransform; + + // Update authority state to catch any possible interpolation data + m_AuthoritativeTransform.Position = transform_.position; + m_AuthoritativeTransform.Rotation = transform_.rotation; + m_AuthoritativeTransform.Scale = transform_.localScale; + + if (!m_OutstandingAuthorityChange) + { + // Keep the anticipated value unchanged, we have no updates from the server at all. + transform_.position = previousAnticipatedTransform.Position; + transform_.localScale = previousAnticipatedTransform.Scale; + transform_.rotation = previousAnticipatedTransform.Rotation; + return; + } + + if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipaionCounter > m_LastAuthorityUpdateCounter) + { + // Keep the anticipated value unchanged because it is more recent than the authoritative one. + transform_.position = previousAnticipatedTransform.Position; + transform_.localScale = previousAnticipatedTransform.Scale; + transform_.rotation = previousAnticipatedTransform.Rotation; + return; + } + + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + m_OutstandingAuthorityChange = false; + m_AnticipatedTransform = m_AuthoritativeTransform; + + ShouldReanticipate = true; + NetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs.meta new file mode 100644 index 0000000000..bb48f930d7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5abfce83aadd948498d4990c645a017b \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Messages.meta b/com.unity.netcode.gameobjects/Runtime/Components/Messages.meta deleted file mode 100644 index fcf8b73d3a..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Components/Messages.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: a9db1d18fa0117f4da5e8e65386b894a -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 73555e2cd8..a1e3ccd657 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -186,7 +186,6 @@ internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) /// NetworkAnimator enables remote synchronization of state for on network objects. ///
[AddComponentMenu("Netcode/Network Animator")] - [RequireComponent(typeof(Animator))] public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver { [Serializable] diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index e0f37e2399..17ebaf77aa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -336,6 +336,9 @@ public void ApplyCurrentTransform() } } + // Used for Rigidbody only (see info on normalized below) + private Vector4 m_QuaternionCheck = Vector4.zero; + /// /// Rotatates the Rigidbody towards a specified rotation /// @@ -353,6 +356,17 @@ public void MoveRotation(Quaternion rotation) } else { + // Evidently we need to check to make sure the quaternion is a perfect + // magnitude of 1.0f when applying the rotation to a rigid body. + m_QuaternionCheck.x = rotation.x; + m_QuaternionCheck.y = rotation.y; + m_QuaternionCheck.z = rotation.z; + m_QuaternionCheck.w = rotation.w; + // If the magnitude is greater than 1.0f (even by a very small fractional value), then normalize the quaternion + if (m_QuaternionCheck.magnitude != 1.0f) + { + rotation.Normalize(); + } m_Rigidbody.MoveRotation(rotation); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index db3e9b83ae..7518ee22b8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -17,6 +17,11 @@ namespace Unity.Netcode.Components [AddComponentMenu("Netcode/Network Transform")] public class NetworkTransform : NetworkBehaviour { + +#if UNITY_EDITOR + internal virtual bool HideInterpolateValue => false; +#endif + #region NETWORK TRANSFORM STATE /// /// Data structure used to synchronize the @@ -2184,10 +2189,15 @@ internal void UpdatePositionInterpolator(Vector3 position, double time, bool res internal bool LogMotion; + protected virtual void OnTransformUpdated() + { + + } + /// /// Applies the authoritative state to the transform /// - private void ApplyAuthoritativeState() + protected internal void ApplyAuthoritativeState() { #if COM_UNITY_MODULES_PHYSICS // TODO: Make this an authority flag @@ -2391,6 +2401,7 @@ private void ApplyAuthoritativeState() } transform.localScale = m_CurrentScale; } + OnTransformUpdated(); } /// @@ -2602,6 +2613,8 @@ private void ApplyTeleportingState(NetworkTransformState newState) { AddLogEntry(ref newState, NetworkObject.OwnerClientId); } + + OnTransformUpdated(); } /// @@ -2769,6 +2782,11 @@ protected virtual void OnNetworkTransformStateUpdated(ref NetworkTransformState } + protected virtual void OnBeforeUpdateTransformState() + { + + } + internal bool LogStateUpdate; /// /// Only non-authoritative instances should invoke this method @@ -2809,6 +2827,10 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf } Debug.Log(builder); } + + // Notification prior to applying a state update + OnBeforeUpdateTransformState(); + // Apply the new state ApplyUpdatedState(newState); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 5b53f1a28e..b0935313b9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -950,7 +950,16 @@ internal void PostNetworkVariableWrite(bool forced = false) // during OnNetworkSpawn has been sent and needs to be cleared for (int i = 0; i < NetworkVariableFields.Count; i++) { - NetworkVariableFields[i].ResetDirty(); + var networkVariable = NetworkVariableFields[i]; + if (networkVariable.IsDirty()) + { + if (networkVariable.CanSend()) + { + networkVariable.UpdateLastSentTime(); + networkVariable.ResetDirty(); + networkVariable.SetDirty(false); + } + } } } else @@ -958,7 +967,16 @@ internal void PostNetworkVariableWrite(bool forced = false) // mark any variables we wrote as no longer dirty for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++) { - NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty(); + var networkVariable = NetworkVariableFields[NetworkVariableIndexesToReset[i]]; + if (networkVariable.IsDirty()) + { + if (networkVariable.CanSend()) + { + networkVariable.UpdateLastSentTime(); + networkVariable.ResetDirty(); + networkVariable.SetDirty(false); + } + } } } @@ -1001,7 +1019,10 @@ internal void NetworkVariableUpdate(ulong targetClientId) networkVariable = NetworkVariableFields[k]; if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId)) { - shouldSend = true; + if (networkVariable.CanSend()) + { + shouldSend = true; + } break; } } @@ -1057,9 +1078,16 @@ private bool CouldHaveDirtyNetworkVariables() // TODO: There should be a better way by reading one dirty variable vs. 'n' for (int i = 0; i < NetworkVariableFields.Count; i++) { - if (NetworkVariableFields[i].IsDirty()) + var networkVariable = NetworkVariableFields[i]; + if (networkVariable.IsDirty()) { - return true; + if (networkVariable.CanSend()) + { + return true; + } + // If it's dirty but can't be sent yet, we have to keep monitoring it until one of the + // conditions blocking its send changes. + NetworkManager.BehaviourUpdater.AddForUpdate(NetworkObject); } } @@ -1268,6 +1296,11 @@ protected virtual void OnSynchronize(ref BufferSerializer serializer) wher } + public virtual void OnReanticipate(double lastRoundTripTime) + { + + } + /// /// The relative client identifier targeted for the serialization of this instance. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index e190702065..7bf2030647 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -11,6 +11,7 @@ public class NetworkBehaviourUpdater private NetworkManager m_NetworkManager; private NetworkConnectionManager m_ConnectionManager; private HashSet m_DirtyNetworkObjects = new HashSet(); + private HashSet m_PendingDirtyNetworkObjects = new HashSet(); #if DEVELOPMENT_BUILD || UNITY_EDITOR private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}"); @@ -18,7 +19,7 @@ public class NetworkBehaviourUpdater internal void AddForUpdate(NetworkObject networkObject) { - m_DirtyNetworkObjects.Add(networkObject); + m_PendingDirtyNetworkObjects.Add(networkObject); } internal void NetworkBehaviourUpdate() @@ -28,6 +29,9 @@ internal void NetworkBehaviourUpdate() #endif try { + m_DirtyNetworkObjects.UnionWith(m_PendingDirtyNetworkObjects); + m_PendingDirtyNetworkObjects.Clear(); + // NetworkObject references can become null, when hidden or despawned. Once NUll, there is no point // trying to process them, even if they were previously marked as dirty. m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b956cfa2c0..f7880d1c3c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -253,8 +253,10 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0); + AnticipationSystem.SetupForUpdate(); MessageManager.ProcessIncomingMessageQueue(); MessageManager.CleanupDisconnectedClients(); + AnticipationSystem.ProcessReanticipation(); } break; #if COM_UNITY_MODULES_PHYSICS @@ -273,6 +275,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) case NetworkUpdateStage.PreUpdate: { NetworkTimeSystem.UpdateTime(); + AnticipationSystem.Update(); } break; case NetworkUpdateStage.PreLateUpdate: @@ -287,6 +290,12 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) } } break; + case NetworkUpdateStage.PostScriptLateUpdate: + { + AnticipationSystem.Sync(); + AnticipationSystem.SetupForRender(); + } + break; case NetworkUpdateStage.PostLateUpdate: { // Handle deferred despawning @@ -526,6 +535,25 @@ public event Action OnTransportFailure remove => ConnectionManager.OnTransportFailure -= value; } + public delegate void ReanticipateDelegate(double lastRoundTripTime); + + /// + /// This callback is called after all individual OnReanticipate calls on AnticipatedNetworkVariable + /// and AnticipatedNetworkTransform values have been invoked. The first parameter is a hash set of + /// all the variables that have been changed on this frame (you can detect a particular variable by + /// checking if the set contains it), while the second parameter is a set of all anticipated network + /// transforms that have been changed. Both are passed as their base class type. + /// + /// The third parameter is the local time corresponding to the current authoritative server state + /// (i.e., to determine the amount of time that needs to be re-simulated, you will use + /// NetworkManager.LocalTime.Time - authorityTime). + /// + public event ReanticipateDelegate OnReanticipate + { + add => AnticipationSystem.OnReanticipate += value; + remove => AnticipationSystem.OnReanticipate -= value; + } + /// /// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection /// @@ -770,6 +798,8 @@ public NetworkPrefabHandler PrefabHandler /// public NetworkTickSystem NetworkTickSystem { get; private set; } + internal AnticipationSystem AnticipationSystem { get; private set; } + /// /// Used for time mocking in tests /// @@ -1078,6 +1108,7 @@ internal void Initialize(bool server) this.RegisterNetworkUpdate(NetworkUpdateStage.FixedUpdate); #endif this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); + this.RegisterNetworkUpdate(NetworkUpdateStage.PostScriptLateUpdate); this.RegisterNetworkUpdate(NetworkUpdateStage.PreLateUpdate); this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate); @@ -1117,6 +1148,7 @@ internal void Initialize(bool server) // The remaining systems can then be initialized NetworkTimeSystem = server ? NetworkTimeSystem.ServerTimeSystem() : new NetworkTimeSystem(1.0 / NetworkConfig.TickRate); NetworkTickSystem = NetworkTimeSystem.Initialize(this); + AnticipationSystem = new AnticipationSystem(this); // Create spawn manager instance SpawnManager = new NetworkSpawnManager(this); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs index cd47065b0a..5f60e748be 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs @@ -54,6 +54,12 @@ public enum NetworkUpdateStage : byte /// PreLateUpdate = 6, /// + /// Updated after Monobehaviour.LateUpdate, but BEFORE rendering + /// + // Yes, these numbers are out of order due to backward compatibility requirements. + // The enum values are listed in the order they will be called. + PostScriptLateUpdate = 8, + /// /// Updated after the Monobehaviour.LateUpdate for all components is invoked /// PostLateUpdate = 7 @@ -258,6 +264,18 @@ public static PlayerLoopSystem CreateLoopSystem() } } + internal struct NetworkPostScriptLateUpdate + { + public static PlayerLoopSystem CreateLoopSystem() + { + return new PlayerLoopSystem + { + type = typeof(NetworkPostScriptLateUpdate), + updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PostScriptLateUpdate) + }; + } + } + internal struct NetworkPostLateUpdate { public static PlayerLoopSystem CreateLoopSystem() @@ -399,6 +417,7 @@ internal static void RegisterLoopSystems() else if (currentSystem.type == typeof(PreLateUpdate)) { TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before); + TryAddLoopSystem(ref currentSystem, NetworkPostScriptLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.After); } else if (currentSystem.type == typeof(PostLateUpdate)) { @@ -440,6 +459,7 @@ internal static void UnregisterLoopSystems() else if (currentSystem.type == typeof(PreLateUpdate)) { TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate)); + TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostScriptLateUpdate)); } else if (currentSystem.type == typeof(PostLateUpdate)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index e51009b985..b7f90f658f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -47,6 +47,8 @@ internal enum NetworkMessageTypes : uint SessionOwner = 20, TimeSync = 21, Unnamed = 22, + AnticipationCounterSyncPingMessage = 23, + AnticipationCounterSyncPongMessage = 24, } @@ -103,7 +105,9 @@ internal enum NetworkMessageTypes : uint { typeof(ServerRpcMessage), NetworkMessageTypes.ServerRpc }, { typeof(TimeSyncMessage), NetworkMessageTypes.TimeSync }, { typeof(UnnamedMessage), NetworkMessageTypes.Unnamed }, - { typeof(SessionOwnerMessage), NetworkMessageTypes.SessionOwner } + { typeof(SessionOwnerMessage), NetworkMessageTypes.SessionOwner }, + { typeof(AnticipationCounterSyncPingMessage), NetworkMessageTypes.AnticipationCounterSyncPingMessage}, + { typeof(AnticipationCounterSyncPongMessage), NetworkMessageTypes.AnticipationCounterSyncPongMessage}, }; // Assure the type to lookup table count and NetworkMessageType enum count matches (i.e. to catch human error when adding new messages) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs new file mode 100644 index 0000000000..b9cdfe7fe4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs @@ -0,0 +1,70 @@ +namespace Unity.Netcode +{ + internal struct AnticipationCounterSyncPingMessage : INetworkMessage + { + public int Version => 0; + + public ulong Counter; + public double Time; + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + BytePacker.WriteValuePacked(writer, Counter); + writer.WriteValueSafe(Time); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsServer) + { + return false; + } + ByteUnpacker.ReadValuePacked(reader, out Counter); + reader.ReadValueSafe(out Time); + return true; + } + + public void Handle(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (networkManager.IsListening && !networkManager.ShutdownInProgress && networkManager.ConnectedClients.ContainsKey(context.SenderId)) + { + var message = new AnticipationCounterSyncPongMessage { Counter = Counter, Time = Time }; + networkManager.MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, context.SenderId); + } + } + } + internal struct AnticipationCounterSyncPongMessage : INetworkMessage + { + public int Version => 0; + + public ulong Counter; + public double Time; + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + BytePacker.WriteValuePacked(writer, Counter); + writer.WriteValueSafe(Time); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return false; + } + ByteUnpacker.ReadValuePacked(reader, out Counter); + reader.ReadValueSafe(out Time); + return true; + } + + public void Handle(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + networkManager.AnticipationSystem.LastAnticipationAck = Counter; + networkManager.AnticipationSystem.LastAnticipationAckTime = Time; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs.meta new file mode 100644 index 0000000000..f4abc895fc --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AnticipationCounterSyncPingMessage.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b7d5c92979ad7e646a078aaf058b53a9 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 4ca9c64eb6..de73302c8a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -69,7 +69,8 @@ public void Serialize(FastBufferWriter writer, int targetVersion) var networkVariable = NetworkBehaviour.NetworkVariableFields[i]; var shouldWrite = networkVariable.IsDirty() && networkVariable.CanClientRead(TargetClientId) && - (networkManager.IsServer || networkVariable.CanClientWrite(networkManager.LocalClientId)); + (networkManager.IsServer || networkVariable.CanClientWrite(networkManager.LocalClientId)) && + networkVariable.CanSend(); // Prevent the server from writing to the client that owns a given NetworkVariable // Allowing the write would send an old value to the client and cause jitter diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs new file mode 100644 index 0000000000..2a08293206 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -0,0 +1,392 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace Unity.Netcode +{ + + public enum StaleDataHandling + { + Ignore, + Reanticipate + } + +#pragma warning disable IDE0001 + /// + /// A variable that can be synchronized over the network. + /// This version supports basic client anticipation - the client can set a value on the belief that the server + /// will update it to reflect the same value in a future update (i.e., as the result of an RPC call). + /// This value can then be adjusted as new updates from the server come in, in three basic modes: + /// + /// + /// + /// Snap: In this mode (with set to + /// and no callback), + /// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value, + /// resulting in a "snap" to the new value if it is different from the anticipated value. + /// + /// Smooth: In this mode (with set to + /// and an callback that calls + /// from the anticipated value to the authority value with an appropriate + /// -style smooth function), when a more up-to-date value is received from the authority, + /// it will interpolate over time from an incorrect anticipated value to the correct authoritative value. + /// + /// Constant Reanticipation: In this mode (with set to + /// and an that calculates a + /// new anticipated value based on the current authoritative value), when a more up-to-date value is received from + /// the authority, user code calculates a new anticipated value, possibly calling to interpolate + /// between the previous anticipation and the new anticipation. This is useful for values that change frequently and + /// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply + /// need a one-time anticipation when the user performs that action. + /// + /// + /// + /// Note that these three modes may be combined. For example, if an callback + /// does not call either or , the result will be a snap to the + /// authoritative value, enabling for a callback that may conditionally call when the + /// difference between the anticipated and authoritative values is within some threshold, but fall back to + /// snap behavior if the difference is too large. + /// + /// the unmanaged type for +#pragma warning restore IDE0001 + [Serializable] + [GenerateSerializationForGenericParameter(0)] + public class AnticipatedNetworkVariable : NetworkVariableBase + { + [SerializeField] + private NetworkVariable m_AuthoritativeValue; + private T m_AnticipatedValue; + private T m_PreviousAnticipatedValue; + private ulong m_LastAuthorityUpdateCounter = 0; + private ulong m_LastAnticipationCounter = 0; + private bool m_IsDisposed = false; + private bool m_SettingAuthoritativeValue = false; + + private T m_SmoothFrom; + private T m_SmoothTo; + private float m_SmoothDuration; + private float m_CurrentSmoothTime; + private bool m_HasSmoothValues; + +#pragma warning disable IDE0001 + /// + /// Defines what the behavior should be if we receive a value from the server with an earlier associated + /// time value than the anticipation time value. + ///

+ /// If this is , the stale data will be ignored and the authoritative + /// value will not replace the anticipated value until the anticipation time is reached. + /// and will also not be invoked for this stale data. + ///

+ /// If this is , the stale data will replace the anticipated data and + /// and will be invoked. + /// In this case, the authoritativeTime value passed to will be lower than + /// the anticipationTime value, and that callback can be used to calculate a new anticipated value. + ///
+#pragma warning restore IDE0001 + public StaleDataHandling StaleDataHandling; + + public delegate void OnAuthoritativeValueChangedDelegate(AnticipatedNetworkVariable variable, in T previousValue, in T newValue); + + /// + /// Invoked any time the authoritative value changes, even when the data is stale or has been changed locally. + /// + public OnAuthoritativeValueChangedDelegate OnAuthoritativeValueChanged = null; + + /// + /// Determines if the difference between the last serialized value and the current value is large enough + /// to serialize it again. + /// + public event NetworkVariable.CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold + { + add => m_AuthoritativeValue.CheckExceedsDirtinessThreshold += value; + remove => m_AuthoritativeValue.CheckExceedsDirtinessThreshold -= value; + } + + private class AnticipatedObject : IAnticipatedObject + { + public AnticipatedNetworkVariable Variable; + + public void Update() + { + Variable.Update(); + } + + public void ResetAnticipation() + { + Variable.ShouldReanticipate = false; + } + + public NetworkObject OwnerObject => Variable.m_NetworkBehaviour.NetworkObject; + } + + private AnticipatedObject m_AnticipatedObject; + + public override void OnInitialize() + { + m_AuthoritativeValue.Initialize(m_NetworkBehaviour); + NetworkVariableSerialization.Duplicate(m_AuthoritativeValue.Value, ref m_AnticipatedValue); + NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); + if (m_NetworkBehaviour != null && m_NetworkBehaviour.NetworkManager != null && m_NetworkBehaviour.NetworkManager.AnticipationSystem != null) + { + m_AnticipatedObject = new AnticipatedObject { Variable = this }; + m_NetworkBehaviour.NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); + } + } + + public override bool ExceedsDirtinessThreshold() + { + return m_AuthoritativeValue.ExceedsDirtinessThreshold(); + } + + /// + /// Retrieves the current value for the variable. + /// This is the "display value" for this variable, and is affected by and + /// , as well as by updates from the authority, depending on + /// and the behavior of any callbacks. + ///

+ /// When a server update arrives, this value will be overwritten + /// by the new server value (unless stale data handling is set + /// to "Ignore" and the update is determined to be stale). + /// This value will be duplicated in + /// , which + /// will NOT be overwritten in server updates. + ///
+ public T Value => m_AnticipatedValue; + + /// + /// Indicates whether this variable currently needs + /// reanticipation. If this is true, the anticipated value + /// has been overwritten by the authoritative value from the + /// server; the previous anticipated value is stored in + /// + public bool ShouldReanticipate + { + get; + private set; + } + + /// + /// Holds the most recent anticipated value, whatever was + /// most recently set using . Unlike + /// , this does not get overwritten + /// when a server update arrives. + /// + public T PreviousAnticipatedValue => m_PreviousAnticipatedValue; + + /// + /// Sets the current value of the variable on the expectation that the authority will set the variable + /// to the same value within one network round trip (i.e., in response to an RPC). + /// + /// + public void Anticipate(T value) + { + if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress || !m_NetworkBehaviour.NetworkManager.IsListening) + { + return; + } + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + m_LastAnticipationCounter = m_NetworkBehaviour.NetworkManager.AnticipationSystem.AnticipationCounter; + m_AnticipatedValue = value; + NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); + if (CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId)) + { + AuthoritativeValue = value; + } + } + +#pragma warning disable IDE0001 + /// + /// Retrieves or sets the underlying authoritative value. + /// Note that only a client or server with write permissions to this variable may set this value. + /// When this variable has been anticipated, this value will alawys return the most recent authoritative + /// state, which is updated even if is . + /// +#pragma warning restore IDE0001 + public T AuthoritativeValue + { + get => m_AuthoritativeValue.Value; + set + { + m_SettingAuthoritativeValue = true; + try + { + m_AuthoritativeValue.Value = value; + m_AnticipatedValue = value; + NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); + } + finally + { + m_SettingAuthoritativeValue = false; + } + } + } + + /// + /// A function to interpolate between two values based on a percentage. + /// See , , , and so on + /// for examples. + /// + public delegate T SmoothDelegate(T authoritativeValue, T anticipatedValue, float amount); + + private SmoothDelegate m_SmoothDelegate = null; + + public AnticipatedNetworkVariable(T value = default, + StaleDataHandling staleDataHandling = StaleDataHandling.Ignore) + : base() + { + StaleDataHandling = staleDataHandling; + m_AuthoritativeValue = new NetworkVariable(value) + { + OnValueChanged = OnValueChangedInternal + }; + } + + public void Update() + { + if (m_CurrentSmoothTime < m_SmoothDuration) + { + m_CurrentSmoothTime += m_NetworkBehaviour.NetworkManager.RealTimeProvider.DeltaTime; + var pct = math.min(m_CurrentSmoothTime / m_SmoothDuration, 1f); + m_AnticipatedValue = m_SmoothDelegate(m_SmoothFrom, m_SmoothTo, pct); + NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); + } + } + + public override void Dispose() + { + if (m_IsDisposed) + { + return; + } + + if (m_NetworkBehaviour != null && m_NetworkBehaviour.NetworkManager != null && m_NetworkBehaviour.NetworkManager.AnticipationSystem != null) + { + if (m_AnticipatedObject != null) + { + m_NetworkBehaviour.NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject); + m_NetworkBehaviour.NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject); + m_AnticipatedObject = null; + } + } + + m_IsDisposed = true; + + m_AuthoritativeValue.Dispose(); + if (m_AnticipatedValue is IDisposable anticipatedValueDisposable) + { + anticipatedValueDisposable.Dispose(); + } + + m_AnticipatedValue = default; + if (m_PreviousAnticipatedValue is IDisposable previousValueDisposable) + { + previousValueDisposable.Dispose(); + m_PreviousAnticipatedValue = default; + } + + if (m_HasSmoothValues) + { + if (m_SmoothFrom is IDisposable smoothFromDisposable) + { + smoothFromDisposable.Dispose(); + m_SmoothFrom = default; + } + if (m_SmoothTo is IDisposable smoothToDisposable) + { + smoothToDisposable.Dispose(); + m_SmoothTo = default; + } + + m_HasSmoothValues = false; + } + } + + ~AnticipatedNetworkVariable() + { + Dispose(); + } + + private void OnValueChangedInternal(T previousValue, T newValue) + { + if (!m_SettingAuthoritativeValue) + { + m_LastAuthorityUpdateCounter = m_NetworkBehaviour.NetworkManager.AnticipationSystem.LastAnticipationAck; + if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipationCounter > m_LastAuthorityUpdateCounter) + { + // Keep the anticipated value unchanged because it is more recent than the authoritative one. + return; + } + + + ShouldReanticipate = true; + m_NetworkBehaviour.NetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject); + } + + NetworkVariableSerialization.Duplicate(AuthoritativeValue, ref m_AnticipatedValue); + + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + OnAuthoritativeValueChanged?.Invoke(this, previousValue, newValue); + } + + /// + /// Interpolate this variable from to over of + /// real time. The duration uses , so it is affected by . + /// + /// + /// + /// + /// + public void Smooth(in T from, in T to, float durationSeconds, SmoothDelegate how) + { + if (durationSeconds <= 0) + { + NetworkVariableSerialization.Duplicate(to, ref m_AnticipatedValue); + m_SmoothDuration = 0; + m_CurrentSmoothTime = 0; + m_SmoothDelegate = null; + return; + } + NetworkVariableSerialization.Duplicate(from, ref m_AnticipatedValue); + NetworkVariableSerialization.Duplicate(from, ref m_SmoothFrom); + NetworkVariableSerialization.Duplicate(to, ref m_SmoothTo); + m_SmoothDuration = durationSeconds; + m_CurrentSmoothTime = 0; + m_SmoothDelegate = how; + m_HasSmoothValues = true; + } + + public override bool IsDirty() + { + return m_AuthoritativeValue.IsDirty(); + } + + public override void ResetDirty() + { + m_AuthoritativeValue.ResetDirty(); + } + + public override void WriteDelta(FastBufferWriter writer) + { + m_AuthoritativeValue.WriteDelta(writer); + } + + public override void WriteField(FastBufferWriter writer) + { + m_AuthoritativeValue.WriteField(writer); + } + + public override void ReadField(FastBufferReader reader) + { + m_AuthoritativeValue.ReadField(reader); + NetworkVariableSerialization.Duplicate(m_AuthoritativeValue.Value, ref m_AnticipatedValue); + NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); + } + + public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) + { + m_AuthoritativeValue.ReadDelta(reader, keepDirtyDelta); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs.meta new file mode 100644 index 0000000000..d3fe73428c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fc9fd5701bee8534a971eb9f49178e21 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index cf4d36edeb..121f7e67b2 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -21,6 +21,29 @@ public class NetworkVariable : NetworkVariableBase /// The callback to be invoked when the value gets changed ///
public OnValueChangedDelegate OnValueChanged; + + public delegate bool CheckExceedsDirtinessThresholdDelegate(in T previousValue, in T newValue); + + public CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold; + + public override bool ExceedsDirtinessThreshold() + { + if (CheckExceedsDirtinessThreshold != null && m_HasPreviousValue) + { + return CheckExceedsDirtinessThreshold(m_PreviousValue, m_InternalValue); + } + + return true; + } + + public override void OnInitialize() + { + base.OnInitialize(); + + m_HasPreviousValue = true; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); + } + internal override NetworkVariableType Type => NetworkVariableType.Value; /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index e142fe9f8e..034d730658 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -3,11 +3,26 @@ namespace Unity.Netcode { + public struct NetworkVariableUpdateTraits + { + [Tooltip("The minimum amount of time that must pass between sending updates. If this amount of time has not passed since the last update, dirtiness will be ignored.")] + public float MinSecondsBetweenUpdates; + + [Tooltip("The maximum amount of time that a variable can be dirty without sending an update. If this amount of time has passed since the last update, an update will be sent even if the dirtiness threshold has not been met.")] + public float MaxSecondsBetweenUpdates; + } + /// /// Interface for network value containers /// public abstract class NetworkVariableBase : IDisposable { + [SerializeField] + internal NetworkVariableUpdateTraits UpdateTraits = default; + + [NonSerialized] + internal double LastUpdateSent; + /// /// The delivery type (QoS) to send data with /// @@ -52,7 +67,42 @@ public void Initialize(NetworkBehaviour networkBehaviour) m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; // When in distributed authority mode, there is no such thing as server write permissions InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; + + if (m_NetworkBehaviour.NetworkManager.NetworkTimeSystem != null) + { + UpdateLastSentTime(); + } } + + OnInitialize(); + } + + /// + /// Called on initialization + /// + public virtual void OnInitialize() + { + + } + + /// + /// Sets the update traits for this network variable to determine how frequently it will send updates. + /// + /// + public void SetUpdateTraits(NetworkVariableUpdateTraits traits) + { + UpdateTraits = traits; + } + + /// + /// Check whether or not this variable has changed significantly enough to send an update. + /// If not, no update will be sent even if the variable is dirty, unless the time since last update exceeds + /// the ' . + /// + /// + public virtual bool ExceedsDirtinessThreshold() + { + return true; } /// @@ -125,6 +175,25 @@ public virtual void SetDirty(bool isDirty) } } + internal bool CanSend() + { + var timeSinceLastUpdate = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime - LastUpdateSent; + return + ( + UpdateTraits.MaxSecondsBetweenUpdates > 0 && + timeSinceLastUpdate >= UpdateTraits.MaxSecondsBetweenUpdates + ) || + ( + timeSinceLastUpdate >= UpdateTraits.MinSecondsBetweenUpdates && + ExceedsDirtinessThreshold() + ); + } + + internal void UpdateLastSentTime() + { + LastUpdateSent = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime; + } + internal static bool IgnoreInitializeWarning; protected void MarkNetworkBehaviourDirty() @@ -147,6 +216,17 @@ protected void MarkNetworkBehaviourDirty() } return; } + + if (!m_NetworkBehaviour.NetworkManager.IsListening) + { + if (m_NetworkBehaviour.NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"NetworkVariable is written to after the NetworkManager has already shutdown! " + + "Are you modifying a NetworkVariable within a NetworkBehaviour.OnDestroy or NetworkBehaviour.OnDespawn method?"); + } + return; + } + m_NetworkBehaviour.NetworkManager.BehaviourUpdater?.AddForUpdate(m_NetworkBehaviour.NetworkObject); } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs index ab2a88600f..5d8f9c7b93 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs @@ -11,6 +11,8 @@ public struct NetworkBehaviourReference : INetworkSerializable, IEquatable /// Creates a new instance of the struct. @@ -21,7 +23,9 @@ public NetworkBehaviourReference(NetworkBehaviour networkBehaviour) { if (networkBehaviour == null) { - throw new ArgumentNullException(nameof(networkBehaviour)); + m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null); + m_NetworkBehaviourId = s_NullId; + return; } if (networkBehaviour.NetworkObject == null) { @@ -60,6 +64,11 @@ public bool TryGet(out T networkBehaviour, NetworkManager networkManager = nu [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null) { + if (networkBehaviourRef.m_NetworkBehaviourId == s_NullId) + { + return null; + } + if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager)) { return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs index dc910440e0..60dcf045d3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs @@ -10,6 +10,7 @@ namespace Unity.Netcode public struct NetworkObjectReference : INetworkSerializable, IEquatable { private ulong m_NetworkObjectId; + private static ulong s_NullId = ulong.MaxValue; /// /// The of the referenced . @@ -30,7 +31,8 @@ public NetworkObjectReference(NetworkObject networkObject) { if (networkObject == null) { - throw new ArgumentNullException(nameof(networkObject)); + m_NetworkObjectId = s_NullId; + return; } if (networkObject.IsSpawned == false) @@ -51,10 +53,16 @@ public NetworkObjectReference(GameObject gameObject) { if (gameObject == null) { - throw new ArgumentNullException(nameof(gameObject)); + m_NetworkObjectId = s_NullId; + return; + } + + var networkObject = gameObject.GetComponent(); + if (!networkObject) + { + throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component."); } - var networkObject = gameObject.GetComponent() ?? throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component."); if (networkObject.IsSpawned == false) { throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s."); @@ -80,10 +88,14 @@ public bool TryGet(out NetworkObject networkObject, NetworkManager networkManage /// /// The reference. /// The networkmanager. Uses to resolve if null. - /// The resolves . Returns null if the networkobject was not found + /// The resolved . Returns null if the networkobject was not found [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null) { + if (networkObjectRef.m_NetworkObjectId == s_NullId) + { + return null; + } networkManager = networkManager ?? NetworkManager.Singleton; networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject); diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs new file mode 100644 index 0000000000..b61afd40b7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; + +namespace Unity.Netcode +{ + internal interface IAnticipationEventReceiver + { + public void SetupForUpdate(); + public void SetupForRender(); + } + + internal interface IAnticipatedObject + { + public void Update(); + public void ResetAnticipation(); + public NetworkObject OwnerObject { get; } + } + + internal class AnticipationSystem + { + internal ulong LastAnticipationAck; + internal double LastAnticipationAckTime; + + internal HashSet AllAnticipatedObjects = new HashSet(); + + internal ulong AnticipationCounter; + + private NetworkManager m_NetworkManager; + + public HashSet ObjectsToReanticipate = new HashSet(); + + public AnticipationSystem(NetworkManager manager) + { + m_NetworkManager = manager; + } + + public event NetworkManager.ReanticipateDelegate OnReanticipate; + + private HashSet m_AnticipationEventReceivers = new HashSet(); + + public void RegisterForAnticipationEvents(IAnticipationEventReceiver receiver) + { + m_AnticipationEventReceivers.Add(receiver); + } + public void DeregisterForAnticipationEvents(IAnticipationEventReceiver receiver) + { + m_AnticipationEventReceivers.Remove(receiver); + } + + public void SetupForUpdate() + { + foreach (var receiver in m_AnticipationEventReceivers) + { + receiver.SetupForUpdate(); + } + } + + public void SetupForRender() + { + foreach (var receiver in m_AnticipationEventReceivers) + { + receiver.SetupForRender(); + } + } + + public void ProcessReanticipation() + { + var lastRoundTripTime = m_NetworkManager.LocalTime.Time - LastAnticipationAckTime; + foreach (var item in ObjectsToReanticipate) + { + foreach (var behaviour in item.OwnerObject.ChildNetworkBehaviours) + { + behaviour.OnReanticipate(lastRoundTripTime); + } + item.ResetAnticipation(); + } + + ObjectsToReanticipate.Clear(); + OnReanticipate?.Invoke(lastRoundTripTime); + } + + public void Update() + { + foreach (var item in AllAnticipatedObjects) + { + item.Update(); + } + } + + public void Sync() + { + if (AllAnticipatedObjects.Count != 0 && !m_NetworkManager.ShutdownInProgress && !m_NetworkManager.ConnectionManager.LocalClient.IsServer && m_NetworkManager.ConnectionManager.LocalClient.IsConnected) + { + var message = new AnticipationCounterSyncPingMessage { Counter = AnticipationCounter, Time = m_NetworkManager.LocalTime.Time }; + m_NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, NetworkManager.ServerClientId); + } + + ++AnticipationCounter; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs.meta b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs.meta new file mode 100644 index 0000000000..9dc824ca41 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4a75eccede7ecf1408f61dd55c338e06 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 71e3349e36..7352c8e80f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -24,6 +24,11 @@ public struct NetworkTime /// public double TickOffset => m_CachedTickOffset; + /// + /// Gets the tick, including partial tick value passed since it started. + /// + public double TickWithPartial => Tick + (TickOffset / m_TickInterval); + /// /// Gets the current time. This is a non fixed time value and similar to . /// diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 16a3c4eade..4dbd85a046 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -5,7 +5,9 @@ namespace Unity.Netcode { /// /// is a standalone system which can be used to run a network time simulation. - /// The network time system maintains both a local and a server time. The local time is based on + /// The network time system maintains both a local and a server time. The local time is based on the server time + /// as last received from the server plus an offset based on the current RTT - in other words, it is a best-guess + /// effort at predicting what the server tick will be when a given network action is processed on the server. /// public class NetworkTimeSystem { diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs index 4ff644e66c..587f2990a3 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/MockTransport.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Random = UnityEngine.Random; namespace Unity.Netcode.TestHelpers.Runtime { @@ -10,6 +11,7 @@ private struct MessageData public ulong FromClientId; public ArraySegment Payload; public NetworkEvent Event; + public float AvailableTime; } private static Dictionary> s_MessageQueue = new Dictionary>(); @@ -18,21 +20,44 @@ private struct MessageData public static ulong HighTransportId = 0; public ulong TransportId = 0; + public float SimulatedLatencySeconds; + public float PacketDropRate; + public float LatencyJitter; public NetworkManager NetworkManager; public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) { + if (Random.Range(0, 1) < PacketDropRate) + { + return; + } var copy = new byte[payload.Array.Length]; Array.Copy(payload.Array, copy, payload.Array.Length); - s_MessageQueue[clientId].Enqueue(new MessageData { FromClientId = TransportId, Payload = new ArraySegment(copy, payload.Offset, payload.Count), Event = NetworkEvent.Data }); + s_MessageQueue[clientId].Enqueue(new MessageData + { + FromClientId = TransportId, + Payload = new ArraySegment(copy, payload.Offset, payload.Count), + Event = NetworkEvent.Data, + AvailableTime = + NetworkManager.RealTimeProvider.UnscaledTime + SimulatedLatencySeconds + Random.Range(-LatencyJitter, LatencyJitter) + }); } public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) { if (s_MessageQueue[TransportId].Count > 0) { - var data = s_MessageQueue[TransportId].Dequeue(); + var data = s_MessageQueue[TransportId].Peek(); + if (data.AvailableTime > NetworkManager.RealTimeProvider.UnscaledTime) + { + clientId = 0; + payload = new ArraySegment(); + receiveTime = 0; + return NetworkEvent.Nothing; + } + + s_MessageQueue[TransportId].Dequeue(); clientId = data.FromClientId; payload = data.Payload; receiveTime = NetworkManager.RealTimeProvider.RealTimeSinceStartup; @@ -90,5 +115,19 @@ public override void Initialize(NetworkManager networkManager = null) { NetworkManager = networkManager; } + + public static void Reset() + { + s_MessageQueue.Clear(); + HighTransportId = 0; + } + + public static void ClearQueues() + { + foreach (var kvp in s_MessageQueue) + { + kvp.Value.Clear(); + } + } } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 3537df28cf..a091e43284 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -337,6 +337,15 @@ public IEnumerator SetUp() NetcodeLogAssert = new NetcodeLogAssert(); if (m_EnableTimeTravel) { + if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests) + { + MockTransport.ClearQueues(); + } + else + { + MockTransport.Reset(); + } + // Setup the frames per tick for time travel advance to next tick ConfigureFramesPerTick(); } @@ -636,6 +645,33 @@ protected void StopOneClientWithTimeTravel(NetworkManager networkManager, bool d Assert.True(WaitForConditionOrTimeOutWithTimeTravel(() => !networkManager.IsConnectedClient)); } + protected void SetTimeTravelSimulatedLatency(float latencySeconds) + { + ((MockTransport)m_ServerNetworkManager.NetworkConfig.NetworkTransport).SimulatedLatencySeconds = latencySeconds; + foreach (var client in m_ClientNetworkManagers) + { + ((MockTransport)client.NetworkConfig.NetworkTransport).SimulatedLatencySeconds = latencySeconds; + } + } + + protected void SetTimeTravelSimulatedDropRate(float dropRatePercent) + { + ((MockTransport)m_ServerNetworkManager.NetworkConfig.NetworkTransport).PacketDropRate = dropRatePercent; + foreach (var client in m_ClientNetworkManagers) + { + ((MockTransport)client.NetworkConfig.NetworkTransport).PacketDropRate = dropRatePercent; + } + } + + protected void SetTimeTravelSimulatedLatencyJitter(float jitterSeconds) + { + ((MockTransport)m_ServerNetworkManager.NetworkConfig.NetworkTransport).LatencyJitter = jitterSeconds; + foreach (var client in m_ClientNetworkManagers) + { + ((MockTransport)client.NetworkConfig.NetworkTransport).LatencyJitter = jitterSeconds; + } + } + /// /// Creates the server and clients /// @@ -1880,8 +1916,21 @@ public static void TimeTravelToNextTick() /// public static void SimulateOneFrame() { - foreach (NetworkUpdateStage stage in Enum.GetValues(typeof(NetworkUpdateStage))) + foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage))) { + var stage = updateStage; + // These two are out of order numerically due to backward compatibility + // requirements. We have to swap them to maintain correct execution + // order. + if (stage == NetworkUpdateStage.PostScriptLateUpdate) + { + stage = NetworkUpdateStage.PostLateUpdate; + } + else if (stage == NetworkUpdateStage.PostLateUpdate) + { + stage = NetworkUpdateStage.PostScriptLateUpdate; + } + NetworkUpdateLoop.RunNetworkUpdateStage(stage); string methodName = string.Empty; switch (stage) @@ -1900,13 +1949,18 @@ public static void SimulateOneFrame() if (!string.IsNullOrEmpty(methodName)) { #if UNITY_2023_1_OR_NEWER - foreach (var behaviour in Object.FindObjectsByType(FindObjectsSortMode.InstanceID)) + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.InstanceID)) #else - foreach (var behaviour in Object.FindObjectsOfType()) + foreach (var obj in Object.FindObjectsOfType()) #endif { - var method = behaviour.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - method?.Invoke(behaviour, new object[] { }); + var method = obj.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + method?.Invoke(obj, new object[] { }); + foreach (var behaviour in obj.ChildNetworkBehaviours) + { + var behaviourMethod = behaviour.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + behaviourMethod?.Invoke(behaviour, new object[] { }); + } } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index 1b872f91cc..195e8b3ea5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -4,6 +4,7 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { @@ -35,7 +36,7 @@ public IEnumerator OnServerStoppedCalledWhenServerStops() m_ServerManager.OnServerStopped += onServerStopped; m_ServerManager.Shutdown(); - UnityEngine.Object.DestroyImmediate(gameObject); + Object.DestroyImmediate(gameObject); yield return WaitUntilManagerShutsdown(); @@ -92,7 +93,7 @@ public IEnumerator OnClientAndServerStoppedCalledWhenHostStops() m_ServerManager.OnServerStopped += onServerStopped; m_ServerManager.OnClientStopped += onClientStopped; m_ServerManager.Shutdown(); - UnityEngine.Object.DestroyImmediate(gameObject); + Object.DestroyImmediate(gameObject); yield return WaitUntilManagerShutsdown(); @@ -228,6 +229,18 @@ in NetworkTimeSystem.Sync */ public virtual IEnumerator Teardown() { NetcodeIntegrationTestHelpers.Destroy(); + if (m_ServerManager != null) + { + m_ServerManager.ShutdownInternal(); + Object.DestroyImmediate(m_ServerManager); + m_ServerManager = null; + } + if (m_ClientManager != null) + { + m_ClientManager.ShutdownInternal(); + Object.DestroyImmediate(m_ClientManager); + m_ClientManager = null; + } yield return null; } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs new file mode 100644 index 0000000000..44530a782c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -0,0 +1,521 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Unity.Netcode.RuntimeTests +{ + internal class NetworkTransformAnticipationComponent : NetworkBehaviour + { + [Rpc(SendTo.Server)] + public void MoveRpc(Vector3 newPosition) + { + transform.position = newPosition; + } + + [Rpc(SendTo.Server)] + public void ScaleRpc(Vector3 newScale) + { + transform.localScale = newScale; + } + + [Rpc(SendTo.Server)] + public void RotateRpc(Quaternion newRotation) + { + transform.rotation = newRotation; + } + + public bool ShouldSmooth = false; + public bool ShouldMove = false; + + public override void OnReanticipate(double lastRoundTripTime) + { + var transform_ = GetComponent(); + if (transform_.ShouldReanticipate) + { + if (ShouldSmooth) + { + transform_.Smooth(transform_.PreviousAnticipatedState, transform_.AuthoritativeState, 1); + } + + if (ShouldMove) + { + transform_.AnticipateMove(transform_.AuthoritativeState.Position + new Vector3(0, 5, 0)); + + } + } + } + } + + internal class NetworkTransformAnticipationTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + protected override bool m_EnableTimeTravel => true; + protected override bool m_SetupIsACoroutine => false; + protected override bool m_TearDownIsACoroutine => false; + + protected override void OnPlayerPrefabGameObjectCreated() + { + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + } + + protected override void OnTimeTravelServerAndClientsConnected() + { + var serverComponent = GetServerComponent(); + var testComponent = GetTestComponent(); + var otherClientComponent = GetOtherClientComponent(); + + serverComponent.transform.position = Vector3.zero; + serverComponent.transform.localScale = Vector3.one; + serverComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward); + testComponent.transform.position = Vector3.zero; + testComponent.transform.localScale = Vector3.one; + testComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward); + otherClientComponent.transform.position = Vector3.zero; + otherClientComponent.transform.localScale = Vector3.one; + otherClientComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward); + } + + public AnticipatedNetworkTransform GetTestComponent() + { + return m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); + } + + public AnticipatedNetworkTransform GetServerComponent() + { + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + if (obj.NetworkManager == m_ServerNetworkManager && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId) + { + return obj; + } + } + + return null; + } + + public AnticipatedNetworkTransform GetOtherClientComponent() + { + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + if (obj.NetworkManager == m_ClientNetworkManagers[1] && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId) + { + return obj; + } + } + + return null; + } + + [Test] + public void WhenAnticipating_ValueChangesImmediately() + { + var testComponent = GetTestComponent(); + + testComponent.AnticipateMove(new Vector3(0, 1, 2)); + testComponent.AnticipateScale(new Vector3(1, 2, 3)); + testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + Assert.AreEqual(new Vector3(0, 1, 2), testComponent.transform.position); + Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.localScale); + Assert.AreEqual(Quaternion.LookRotation(new Vector3(2, 3, 4)), testComponent.transform.rotation); + + Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Scale); + Assert.AreEqual(Quaternion.LookRotation(new Vector3(2, 3, 4)), testComponent.AnticipatedState.Rotation); + + } + + [Test] + public void WhenAnticipating_AuthoritativeValueDoesNotChange() + { + var testComponent = GetTestComponent(); + + var startPosition = testComponent.transform.position; + var startScale = testComponent.transform.localScale; + var startRotation = testComponent.transform.rotation; + + testComponent.AnticipateMove(new Vector3(0, 1, 2)); + testComponent.AnticipateScale(new Vector3(1, 2, 3)); + testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + Assert.AreEqual(startPosition, testComponent.AuthoritativeState.Position); + Assert.AreEqual(startScale, testComponent.AuthoritativeState.Scale); + Assert.AreEqual(startRotation, testComponent.AuthoritativeState.Rotation); + } + + [Test] + public void WhenAnticipating_ServerDoesNotChange() + { + var testComponent = GetTestComponent(); + + var startPosition = testComponent.transform.position; + var startScale = testComponent.transform.localScale; + var startRotation = testComponent.transform.rotation; + + testComponent.AnticipateMove(new Vector3(0, 1, 2)); + testComponent.AnticipateScale(new Vector3(1, 2, 3)); + testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + var serverComponent = GetServerComponent(); + + Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); + Assert.AreEqual(startScale, serverComponent.AuthoritativeState.Scale); + Assert.AreEqual(startRotation, serverComponent.AuthoritativeState.Rotation); + Assert.AreEqual(startPosition, serverComponent.AnticipatedState.Position); + Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); + Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + + TimeTravel(2, 120); + + Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); + Assert.AreEqual(startScale, serverComponent.AuthoritativeState.Scale); + Assert.AreEqual(startRotation, serverComponent.AuthoritativeState.Rotation); + Assert.AreEqual(startPosition, serverComponent.AnticipatedState.Position); + Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); + Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + } + + [Test] + public void WhenAnticipating_OtherClientDoesNotChange() + { + var testComponent = GetTestComponent(); + + var startPosition = testComponent.transform.position; + var startScale = testComponent.transform.localScale; + var startRotation = testComponent.transform.rotation; + + testComponent.AnticipateMove(new Vector3(0, 1, 2)); + testComponent.AnticipateScale(new Vector3(1, 2, 3)); + testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + var otherClientComponent = GetOtherClientComponent(); + + Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); + Assert.AreEqual(startScale, otherClientComponent.AuthoritativeState.Scale); + Assert.AreEqual(startRotation, otherClientComponent.AuthoritativeState.Rotation); + Assert.AreEqual(startPosition, otherClientComponent.AnticipatedState.Position); + Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); + Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + + TimeTravel(2, 120); + + Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); + Assert.AreEqual(startScale, otherClientComponent.AuthoritativeState.Scale); + Assert.AreEqual(startRotation, otherClientComponent.AuthoritativeState.Rotation); + Assert.AreEqual(startPosition, otherClientComponent.AnticipatedState.Position); + Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); + Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + } + + [Test] + public void WhenServerChangesSnapValue_ValuesAreUpdated() + { + var testComponent = GetTestComponent(); + var serverComponent = GetServerComponent(); + serverComponent.Interpolate = false; + + testComponent.AnticipateMove(new Vector3(0, 1, 2)); + testComponent.AnticipateScale(new Vector3(1, 2, 3)); + testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + + var rpcComponent = testComponent.GetComponent(); + rpcComponent.MoveRpc(new Vector3(2, 3, 4)); + + WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + var otherClientComponent = GetOtherClientComponent(); + + WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == serverComponent.transform.position && otherClientComponent.AuthoritativeState.Position == serverComponent.transform.position); + + Assert.AreEqual(serverComponent.transform.position, testComponent.transform.position); + Assert.AreEqual(serverComponent.transform.position, testComponent.AnticipatedState.Position); + Assert.AreEqual(serverComponent.transform.position, testComponent.AuthoritativeState.Position); + + Assert.AreEqual(serverComponent.transform.position, otherClientComponent.transform.position); + Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AnticipatedState.Position); + Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AuthoritativeState.Position); + } + + public void AssertQuaternionsAreEquivalent(Quaternion a, Quaternion b) + { + var aAngles = a.eulerAngles; + var bAngles = b.eulerAngles; + Assert.AreEqual(aAngles.x, bAngles.x, 0.001, $"Quaternions were not equal. Expected: {a}, but was {b}"); + Assert.AreEqual(aAngles.y, bAngles.y, 0.001, $"Quaternions were not equal. Expected: {a}, but was {b}"); + Assert.AreEqual(aAngles.z, bAngles.z, 0.001, $"Quaternions were not equal. Expected: {a}, but was {b}"); + } + public void AssertVectorsAreEquivalent(Vector3 a, Vector3 b) + { + Assert.AreEqual(a.x, b.x, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); + Assert.AreEqual(a.y, b.y, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); + Assert.AreEqual(a.z, b.z, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}"); + } + + [Test] + public void WhenServerChangesSmoothValue_ValuesAreLerped() + { + var testComponent = GetTestComponent(); + var otherClientComponent = GetOtherClientComponent(); + + testComponent.StaleDataHandling = StaleDataHandling.Ignore; + otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore; + + var serverComponent = GetServerComponent(); + serverComponent.Interpolate = false; + + testComponent.GetComponent().ShouldSmooth = true; + otherClientComponent.GetComponent().ShouldSmooth = true; + + var startPosition = testComponent.transform.position; + var startScale = testComponent.transform.localScale; + var startRotation = testComponent.transform.rotation; + var anticipePosition = new Vector3(0, 1, 2); + var anticipeScale = new Vector3(1, 2, 3); + var anticipeRotation = Quaternion.LookRotation(new Vector3(2, 3, 4)); + var serverSetPosition = new Vector3(3, 4, 5); + var serverSetScale = new Vector3(4, 5, 6); + var serverSetRotation = Quaternion.LookRotation(new Vector3(5, 6, 7)); + + testComponent.AnticipateMove(anticipePosition); + testComponent.AnticipateScale(anticipeScale); + testComponent.AnticipateRotate(anticipeRotation); + + var rpcComponent = testComponent.GetComponent(); + rpcComponent.MoveRpc(serverSetPosition); + rpcComponent.RotateRpc(serverSetRotation); + rpcComponent.ScaleRpc(serverSetScale); + + WaitForMessagesReceivedWithTimeTravel(new List + { + typeof(RpcMessage), + typeof(RpcMessage), + typeof(RpcMessage), + }, new List { m_ServerNetworkManager }); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + var percentChanged = 1f / 60f; + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + + for (var i = 1; i < 60; ++i) + { + TimeTravel(1f / 60f, 1); + percentChanged = 1f / 60f * (i + 1); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + } + TimeTravel(1f / 60f, 1); + + AssertVectorsAreEquivalent(serverSetPosition, testComponent.transform.position); + AssertVectorsAreEquivalent(serverSetScale, testComponent.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.transform.rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testComponent.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, testComponent.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.transform.position); + AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.transform.rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + } + + [Test] + public void WhenServerChangesReanticipeValue_ValuesAreReanticiped() + { + var testComponent = GetTestComponent(); + var otherClientComponent = GetOtherClientComponent(); + + testComponent.GetComponent().ShouldMove = true; + otherClientComponent.GetComponent().ShouldMove = true; + + var serverComponent = GetServerComponent(); + serverComponent.Interpolate = false; + serverComponent.transform.position = new Vector3(0, 1, 2); + var rpcComponent = testComponent.GetComponent(); + rpcComponent.MoveRpc(new Vector3(0, 1, 2)); + + WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + Assert.AreEqual(new Vector3(0, 6, 2), testComponent.transform.position); + Assert.AreEqual(new Vector3(0, 6, 2), testComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AuthoritativeState.Position); + + Assert.AreEqual(new Vector3(0, 6, 2), otherClientComponent.transform.position); + Assert.AreEqual(new Vector3(0, 6, 2), otherClientComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(0, 1, 2), otherClientComponent.AuthoritativeState.Position); + } + + [Test] + public void WhenStaleDataArrivesToIgnoreVariable_ItIsIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames) + { + m_ServerNetworkManager.NetworkConfig.TickRate = tickRate; + m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate; + + for (var i = 0; i < skipFrames; ++i) + { + TimeTravel(1 / 60f, 1); + } + + var serverComponent = GetServerComponent(); + serverComponent.Interpolate = false; + + var testComponent = GetTestComponent(); + testComponent.StaleDataHandling = StaleDataHandling.Ignore; + testComponent.Interpolate = false; + + var otherClientComponent = GetOtherClientComponent(); + otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore; + otherClientComponent.Interpolate = false; + + var rpcComponent = testComponent.GetComponent(); + rpcComponent.MoveRpc(new Vector3(1, 2, 3)); + + WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + + testComponent.AnticipateMove(new Vector3(0, 5, 0)); + rpcComponent.MoveRpc(new Vector3(4, 5, 6)); + + // Depending on tick rate, one of these two things will happen. + // The assertions are different based on this... either the tick rate is slow enough that the second RPC is received + // before the next update and we move to 4, 5, 6, or the tick rate is fast enough that the next update is sent out + // before the RPC is received and we get the update for the move to 1, 2, 3. Both are valid, what we want to assert + // here is that the anticipated state never becomes 1, 2, 3. + WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == new Vector3(1, 2, 3) || testComponent.AuthoritativeState.Position == new Vector3(4, 5, 6)); + + if (testComponent.AnticipatedState.Position == new Vector3(4, 5, 6)) + { + // Anticiped client received this data for a time earlier than its anticipation, and should have prioritized the anticiped value + Assert.AreEqual(new Vector3(4, 5, 6), testComponent.transform.position); + Assert.AreEqual(new Vector3(4, 5, 6), testComponent.AnticipatedState.Position); + // However, the authoritative value still gets updated + Assert.AreEqual(new Vector3(4, 5, 6), testComponent.AuthoritativeState.Position); + + // Other client got the server value and had made no anticipation, so it applies it to the anticiped value as well. + Assert.AreEqual(new Vector3(4, 5, 6), otherClientComponent.transform.position); + Assert.AreEqual(new Vector3(4, 5, 6), otherClientComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(4, 5, 6), otherClientComponent.AuthoritativeState.Position); + } + else + { + // Anticiped client received this data for a time earlier than its anticipation, and should have prioritized the anticiped value + Assert.AreEqual(new Vector3(0, 5, 0), testComponent.transform.position); + Assert.AreEqual(new Vector3(0, 5, 0), testComponent.AnticipatedState.Position); + // However, the authoritative value still gets updated + Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AuthoritativeState.Position); + + // Other client got the server value and had made no anticipation, so it applies it to the anticiped value as well. + Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.transform.position); + Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AuthoritativeState.Position); + } + } + + + [Test] + public void WhenNonStaleDataArrivesToIgnoreVariable_ItIsNotIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames) + { + m_ServerNetworkManager.NetworkConfig.TickRate = tickRate; + m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate; + + for (var i = 0; i < skipFrames; ++i) + { + TimeTravel(1 / 60f, 1); + } + + var serverComponent = GetServerComponent(); + serverComponent.Interpolate = false; + + var testComponent = GetTestComponent(); + testComponent.StaleDataHandling = StaleDataHandling.Ignore; + testComponent.Interpolate = false; + + var otherClientComponent = GetOtherClientComponent(); + otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore; + otherClientComponent.Interpolate = false; + + testComponent.AnticipateMove(new Vector3(0, 5, 0)); + var rpcComponent = testComponent.GetComponent(); + rpcComponent.MoveRpc(new Vector3(1, 2, 3)); + + WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + + WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == serverComponent.transform.position && otherClientComponent.AuthoritativeState.Position == serverComponent.transform.position); + + // Anticiped client received this data for a time earlier than its anticipation, and should have prioritized the anticiped value + Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.position); + Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Position); + // However, the authoritative value still gets updated + Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AuthoritativeState.Position); + + // Other client got the server value and had made no anticipation, so it applies it to the anticiped value as well. + Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.transform.position); + Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AuthoritativeState.Position); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs.meta new file mode 100644 index 0000000000..ae75b6b866 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ceb074b080c27184a9f669cd68355955 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs new file mode 100644 index 0000000000..c436275fea --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs @@ -0,0 +1,420 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Unity.Netcode.RuntimeTests +{ + internal class NetworkVariableAnticipationComponent : NetworkBehaviour + { + public AnticipatedNetworkVariable SnapOnAnticipationFailVariable = new AnticipatedNetworkVariable(0, StaleDataHandling.Ignore); + public AnticipatedNetworkVariable SmoothOnAnticipationFailVariable = new AnticipatedNetworkVariable(0, StaleDataHandling.Reanticipate); + public AnticipatedNetworkVariable ReanticipateOnAnticipationFailVariable = new AnticipatedNetworkVariable(0, StaleDataHandling.Reanticipate); + + public override void OnReanticipate(double lastRoundTripTime) + { + if (SmoothOnAnticipationFailVariable.ShouldReanticipate) + { + if (Mathf.Abs(SmoothOnAnticipationFailVariable.AuthoritativeValue - SmoothOnAnticipationFailVariable.PreviousAnticipatedValue) > Mathf.Epsilon) + { + SmoothOnAnticipationFailVariable.Smooth(SmoothOnAnticipationFailVariable.PreviousAnticipatedValue, SmoothOnAnticipationFailVariable.AuthoritativeValue, 1, Mathf.Lerp); + } + } + + if (ReanticipateOnAnticipationFailVariable.ShouldReanticipate) + { + // Would love to test some stuff about anticipation based on time, but that is difficult to test accurately. + // This reanticipating variable will just always anticipate a value 5 higher than the server value. + ReanticipateOnAnticipationFailVariable.Anticipate(ReanticipateOnAnticipationFailVariable.AuthoritativeValue + 5); + } + } + + public bool SnapRpcResponseReceived = false; + + [Rpc(SendTo.Server)] + public void SetSnapValueRpc(int i, RpcParams rpcParams = default) + { + SnapOnAnticipationFailVariable.AuthoritativeValue = i; + SetSnapValueResponseRpc(RpcTarget.Single(rpcParams.Receive.SenderClientId, RpcTargetUse.Temp)); + } + + [Rpc(SendTo.SpecifiedInParams)] + public void SetSnapValueResponseRpc(RpcParams rpcParams) + { + SnapRpcResponseReceived = true; + } + + [Rpc(SendTo.Server)] + public void SetSmoothValueRpc(float f) + { + SmoothOnAnticipationFailVariable.AuthoritativeValue = f; + } + + [Rpc(SendTo.Server)] + public void SetReanticipateValueRpc(float f) + { + ReanticipateOnAnticipationFailVariable.AuthoritativeValue = f; + } + } + + internal class NetworkVariableAnticipationTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + protected override bool m_EnableTimeTravel => true; + protected override bool m_SetupIsACoroutine => false; + protected override bool m_TearDownIsACoroutine => false; + + protected override void OnPlayerPrefabGameObjectCreated() + { + m_PlayerPrefab.AddComponent(); + } + + public NetworkVariableAnticipationComponent GetTestComponent() + { + return m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); + } + + public NetworkVariableAnticipationComponent GetServerComponent() + { + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + if (obj.NetworkManager == m_ServerNetworkManager && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId) + { + return obj; + } + } + + return null; + } + + public NetworkVariableAnticipationComponent GetOtherClientComponent() + { + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + if (obj.NetworkManager == m_ClientNetworkManagers[1] && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId) + { + return obj; + } + } + + return null; + } + + [Test] + public void WhenAnticipating_ValueChangesImmediately() + { + var testComponent = GetTestComponent(); + + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + testComponent.SmoothOnAnticipationFailVariable.Anticipate(15); + testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20); + + Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(15, testComponent.SmoothOnAnticipationFailVariable.Value); + Assert.AreEqual(20, testComponent.ReanticipateOnAnticipationFailVariable.Value); + } + + [Test] + public void WhenAnticipating_AuthoritativeValueDoesNotChange() + { + var testComponent = GetTestComponent(); + + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + testComponent.SmoothOnAnticipationFailVariable.Anticipate(15); + testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20); + + Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + } + + [Test] + public void WhenAnticipating_ServerDoesNotChange() + { + var testComponent = GetTestComponent(); + + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + testComponent.SmoothOnAnticipationFailVariable.Anticipate(15); + testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20); + + var serverComponent = GetServerComponent(); + + Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.Value); + Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.Value); + + TimeTravel(2, 120); + + Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.Value); + Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.Value); + } + + [Test] + public void WhenAnticipating_OtherClientDoesNotChange() + { + var testComponent = GetTestComponent(); + + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + testComponent.SmoothOnAnticipationFailVariable.Anticipate(15); + testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20); + + var otherClientComponent = GetOtherClientComponent(); + + Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.Value); + Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value); + + TimeTravel(2, 120); + + Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.Value); + Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value); + } + + [Test] + public void WhenServerChangesSnapValue_ValuesAreUpdated() + { + var testComponent = GetTestComponent(); + + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + + Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + testComponent.SetSnapValueRpc(10); + + WaitForMessageReceivedWithTimeTravel( + new List { m_ServerNetworkManager } + ); + + var serverComponent = GetServerComponent(); + Assert.AreEqual(10, serverComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(10, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + var otherClientComponent = GetOtherClientComponent(); + Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + Assert.AreEqual(10, otherClientComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(10, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + } + + [Test] + public void WhenServerChangesSmoothValue_ValuesAreLerped() + { + var testComponent = GetTestComponent(); + + testComponent.SmoothOnAnticipationFailVariable.Anticipate(15); + + Assert.AreEqual(15, testComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(0, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + // Set to a different value to simulate a anticipation failure - will lerp between the anticipated value + // and the actual one + testComponent.SetSmoothValueRpc(20); + + WaitForMessageReceivedWithTimeTravel( + new List { m_ServerNetworkManager } + ); + + var serverComponent = GetServerComponent(); + Assert.AreEqual(20, serverComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(20, serverComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + var otherClientComponent = GetOtherClientComponent(); + Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + Assert.AreEqual(15 + 1f / 60f * 5, testComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(20, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + Assert.AreEqual(0 + 1f / 60f * 20, otherClientComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(20, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + for (var i = 1; i < 60; ++i) + { + TimeTravel(1f / 60f, 1); + + Assert.AreEqual(15 + 1f / 60f * 5 * (i + 1), testComponent.SmoothOnAnticipationFailVariable.Value, 0.00001); + Assert.AreEqual(20, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + Assert.AreEqual(0 + 1f / 60f * 20 * (i + 1), otherClientComponent.SmoothOnAnticipationFailVariable.Value, 0.00001); + Assert.AreEqual(20, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + } + TimeTravel(1f / 60f, 1); + Assert.AreEqual(20, testComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(20, otherClientComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon); + } + + [Test] + public void WhenServerChangesReanticipateValue_ValuesAreReanticipated() + { + var testComponent = GetTestComponent(); + + testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(15); + + Assert.AreEqual(15, testComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(0, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + // Set to a different value to simulate a anticipation failure - will lerp between the anticipated value + // and the actual one + testComponent.SetReanticipateValueRpc(20); + + WaitForMessageReceivedWithTimeTravel( + new List { m_ServerNetworkManager } + ); + + var serverComponent = GetServerComponent(); + Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + var otherClientComponent = GetOtherClientComponent(); + Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + Assert.AreEqual(25, testComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(20, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + + Assert.AreEqual(25, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon); + Assert.AreEqual(20, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon); + } + + [Test] + public void WhenNonStaleDataArrivesToIgnoreVariable_ItIsNotIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames) + { + m_ServerNetworkManager.NetworkConfig.TickRate = tickRate; + m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate; + + for (var i = 0; i < skipFrames; ++i) + { + TimeTravel(1 / 60f, 1); + } + var testComponent = GetTestComponent(); + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + + Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + testComponent.SetSnapValueRpc(20); + WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + + var serverComponent = GetServerComponent(); + + Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + // Both values get updated + Assert.AreEqual(20, testComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(20, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + // Other client got the server value and had made no anticipation, so it applies it to the anticipated value as well. + var otherClientComponent = GetOtherClientComponent(); + Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + } + + [Test] + public void WhenStaleDataArrivesToIgnoreVariable_ItIsIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames) + { + m_ServerNetworkManager.NetworkConfig.TickRate = tickRate; + m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate; + + for (var i = 0; i < skipFrames; ++i) + { + TimeTravel(1 / 60f, 1); + } + var testComponent = GetTestComponent(); + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + + Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + testComponent.SetSnapValueRpc(30); + + var serverComponent = GetServerComponent(); + serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue = 20; + + Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + if (testComponent.SnapRpcResponseReceived) + { + // In this case the tick rate is slow enough that the RPC was received and processed, so we check that. + Assert.AreEqual(30, testComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(30, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + var otherClientComponent = GetOtherClientComponent(); + Assert.AreEqual(30, otherClientComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(30, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + } + else + { + // In this case, we got an update before the RPC was processed, so we should have ignored it. + // Anticipated client received this data for a tick earlier than its anticipation, and should have prioritized the anticipated value + Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value); + // However, the authoritative value still gets updated + Assert.AreEqual(20, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + + // Other client got the server value and had made no anticipation, so it applies it to the anticipated value as well. + var otherClientComponent = GetOtherClientComponent(); + Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.Value); + Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue); + } + } + + [Test] + public void WhenStaleDataArrivesToReanticipatedVariable_ItIsAppliedAndReanticipated() + { + var testComponent = GetTestComponent(); + testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(10); + + Assert.AreEqual(10, testComponent.ReanticipateOnAnticipationFailVariable.Value); + Assert.AreEqual(0, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + + var serverComponent = GetServerComponent(); + serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue = 20; + + Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.Value); + Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + + Assert.AreEqual(25, testComponent.ReanticipateOnAnticipationFailVariable.Value); + // However, the authoritative value still gets updated + Assert.AreEqual(20, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + + // Other client got the server value and had made no anticipation, so it applies it to the anticipated value as well. + var otherClientComponent = GetOtherClientComponent(); + Assert.AreEqual(25, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value); + Assert.AreEqual(20, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs.meta new file mode 100644 index 0000000000..357da56e2d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableAnticipationTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 74e627a9d18dcd04e9c56ab2539a6593 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index 83b4633ce4..e59fad1131 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -386,9 +386,12 @@ public override int GetHashCode() // Used just to create a NetworkVariable in the templated NetworkBehaviour type that isn't referenced anywhere else // Please do not reference this class anywhere else! - internal class TestClass_ReferencedOnlyByTemplateNetworkBehavourType : TestClass + internal class TestClass_ReferencedOnlyByTemplateNetworkBehaviourType : TestClass, IEquatable { - + public bool Equals(TestClass_ReferencedOnlyByTemplateNetworkBehaviourType other) + { + return Equals((TestClass)other); + } } internal class NetworkVariableTest : NetworkBehaviour @@ -921,7 +924,7 @@ bool VerifyClass() m_Player1OnClient1.GetComponent().TheVar.Value.SomeInt == m_Player1OnServer.GetComponent().TheVar.Value.SomeInt; } - m_Player1OnServer.GetComponent().TheVar.Value = new TestClass_ReferencedOnlyByTemplateNetworkBehavourType { SomeInt = k_TestUInt, SomeBool = false }; + m_Player1OnServer.GetComponent().TheVar.Value = new TestClass_ReferencedOnlyByTemplateNetworkBehaviourType { SomeInt = k_TestUInt, SomeBool = false }; m_Player1OnServer.GetComponent().TheVar.SetDirty(true); // Wait for the client-side to notify it is finished initializing and spawning. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs index 004de73550..f50811ba1f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs @@ -903,7 +903,7 @@ internal class ClassHavingNetworkBehaviour : IntermediateNetworkBehavior + internal class ClassHavingNetworkBehaviour2 : TemplateNetworkBehaviourType { } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs new file mode 100644 index 0000000000..66c326cb99 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs @@ -0,0 +1,138 @@ +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Unity.Netcode.RuntimeTests +{ + internal class NetworkVariableTraitsComponent : NetworkBehaviour + { + public NetworkVariable TheVariable = new NetworkVariable(); + } + + internal class NetworkVariableTraitsTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + protected override bool m_EnableTimeTravel => true; + protected override bool m_SetupIsACoroutine => false; + protected override bool m_TearDownIsACoroutine => false; + + protected override void OnPlayerPrefabGameObjectCreated() + { + m_PlayerPrefab.AddComponent(); + } + + public NetworkVariableTraitsComponent GetTestComponent() + { + return m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); + } + + public NetworkVariableTraitsComponent GetServerComponent() + { + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + if (obj.NetworkManager == m_ServerNetworkManager && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId) + { + return obj; + } + } + + return null; + } + + [Test] + public void WhenNewValueIsLessThanThreshold_VariableIsNotSerialized() + { + var serverComponent = GetServerComponent(); + var testComponent = GetTestComponent(); + serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1; + + serverComponent.TheVariable.Value = 0.05f; + + TimeTravel(2, 120); + + Assert.AreEqual(0.05f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0, testComponent.TheVariable.Value); ; + } + [Test] + public void WhenNewValueIsGreaterThanThreshold_VariableIsSerialized() + { + var serverComponent = GetServerComponent(); + var testComponent = GetTestComponent(); + serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1; + + serverComponent.TheVariable.Value = 0.15f; + + TimeTravel(2, 120); + + Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0.15f, testComponent.TheVariable.Value); ; + } + + [Test] + public void WhenNewValueIsLessThanThresholdButMaxTimeHasPassed_VariableIsSerialized() + { + var serverComponent = GetServerComponent(); + var testComponent = GetTestComponent(); + serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1; + serverComponent.TheVariable.SetUpdateTraits(new NetworkVariableUpdateTraits { MaxSecondsBetweenUpdates = 2 }); + serverComponent.TheVariable.LastUpdateSent = m_ServerNetworkManager.NetworkTimeSystem.LocalTime; + + serverComponent.TheVariable.Value = 0.05f; + + TimeTravel(1 / 60f * 119, 119); + + Assert.AreEqual(0.05f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0, testComponent.TheVariable.Value); ; + + TimeTravel(1 / 60f * 4, 4); + + Assert.AreEqual(0.05f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0.05f, testComponent.TheVariable.Value); ; + } + + [Test] + public void WhenNewValueIsGreaterThanThresholdButMinTimeHasNotPassed_VariableIsNotSerialized() + { + var serverComponent = GetServerComponent(); + var testComponent = GetTestComponent(); + serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1; + serverComponent.TheVariable.SetUpdateTraits(new NetworkVariableUpdateTraits { MinSecondsBetweenUpdates = 2 }); + serverComponent.TheVariable.LastUpdateSent = m_ServerNetworkManager.NetworkTimeSystem.LocalTime; + + serverComponent.TheVariable.Value = 0.15f; + + TimeTravel(1 / 60f * 119, 119); + + Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0, testComponent.TheVariable.Value); ; + + TimeTravel(1 / 60f * 4, 4); + + Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0.15f, testComponent.TheVariable.Value); ; + } + + [Test] + public void WhenNoThresholdIsSetButMinTimeHasNotPassed_VariableIsNotSerialized() + { + var serverComponent = GetServerComponent(); + var testComponent = GetTestComponent(); + serverComponent.TheVariable.SetUpdateTraits(new NetworkVariableUpdateTraits { MinSecondsBetweenUpdates = 2 }); + serverComponent.TheVariable.LastUpdateSent = m_ServerNetworkManager.NetworkTimeSystem.LocalTime; + + serverComponent.TheVariable.Value = 0.15f; + + TimeTravel(1 / 60f * 119, 119); + + Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0, testComponent.TheVariable.Value); ; + + TimeTravel(1 / 60f * 4, 4); + + Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ; + Assert.AreEqual(0.15f, testComponent.TheVariable.Value); ; + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs.meta new file mode 100644 index 0000000000..3bfb25d690 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTraitsTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 49f46ca2b4327464498a7465891647bb \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs index 3c61d9258b..c261bdf032 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableUserSerializableTypesTests.cs @@ -112,6 +112,16 @@ public NetworkVariableUserSerializableTypesTests() protected override IEnumerator OnSetup() { WorkingUserNetworkVariableComponentBase.Reset(); + + UserNetworkVariableSerialization.WriteValue = null; + UserNetworkVariableSerialization.ReadValue = null; + UserNetworkVariableSerialization.DuplicateValue = null; + UserNetworkVariableSerialization.WriteValue = null; + UserNetworkVariableSerialization.ReadValue = null; + UserNetworkVariableSerialization.DuplicateValue = null; + UserNetworkVariableSerialization.WriteValue = null; + UserNetworkVariableSerialization.ReadValue = null; + UserNetworkVariableSerialization.DuplicateValue = null; return base.OnSetup(); } @@ -217,5 +227,37 @@ public void WhenUsingAUserSerializableNetworkVariableWithoutUserSerialization_Re } ); } + + protected override IEnumerator OnTearDown() + { + // These have to get set to SOMETHING, otherwise we will get an exception thrown because Object.Destroy() + // calls __initializeNetworkVariables, and the network variable initialization attempts to call FallbackSerializer, + // which throws an exception if any of these values are null. They don't have to DO anything, they just have to + // be non-null to keep the test from failing during teardown. + // None of this is related to what's being tested above, and in reality, these values being null is an invalid + // use case. But one of the tests is explicitly testing that invalid use case, and the values are being set + // to null in OnSetup to ensure test isolation. This wouldn't be a situation a user would have to think about + // in a real world use case. + UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in MyTypeOne value) => { }; + UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out MyTypeOne value) => { value = new MyTypeOne(); }; + UserNetworkVariableSerialization.DuplicateValue = (in MyTypeOne value, ref MyTypeOne duplicatedValue) => + { + duplicatedValue = value; + }; + UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in MyTypeTwo value) => { }; + UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out MyTypeTwo value) => { value = new MyTypeTwo(); }; + UserNetworkVariableSerialization.DuplicateValue = (in MyTypeTwo value, ref MyTypeTwo duplicatedValue) => + { + duplicatedValue = value; + }; + UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in MyTypeThree value) => { }; + UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out MyTypeThree value) => { value = new MyTypeThree(); }; + UserNetworkVariableSerialization.DuplicateValue = (in MyTypeThree value, ref MyTypeThree duplicatedValue) => + { + duplicatedValue = value; + }; + return base.OnTearDown(); + } } } + diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index c3f63b941f..6b7d497738 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -17,6 +17,8 @@ internal class NetworkBehaviourReferenceTests : IDisposable { private class TestNetworkBehaviour : NetworkBehaviour { + public static bool ReceivedRPC; + public NetworkVariable TestVariable = new NetworkVariable(); public TestNetworkBehaviour RpcReceivedBehaviour; @@ -25,6 +27,7 @@ private class TestNetworkBehaviour : NetworkBehaviour public void SendReferenceServerRpc(NetworkBehaviourReference value) { RpcReceivedBehaviour = (TestNetworkBehaviour)value; + ReceivedRPC = true; } } @@ -57,8 +60,43 @@ public IEnumerator TestRpc() Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); } + [UnityTest] + public IEnumerator TestSerializeNull([Values] bool initializeWithNull) + { + TestNetworkBehaviour.ReceivedRPC = false; + using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); + var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); + networkObjectContext.Object.Spawn(); + using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); + otherObjectContext.Object.Spawn(); + // If not initializing with null, then use the default constructor with no assigned NetworkBehaviour + if (!initializeWithNull) + { + testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference()); + } + else // Otherwise, initialize and pass in null as the reference + { + testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(null)); + } + + // wait for rpc completion + float t = 0; + while (!TestNetworkBehaviour.ReceivedRPC) + { + t += Time.deltaTime; + if (t > 5f) + { + new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); + } + + yield return null; + } + + // validate + Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedBehaviour); + } [UnityTest] public IEnumerator TestRpcImplicitNetworkBehaviour() @@ -131,15 +169,6 @@ public void FailSerializeGameObjectWithoutNetworkObject() }); } - [Test] - public void FailSerializeNullBehaviour() - { - Assert.Throws(() => - { - NetworkBehaviourReference outReference = null; - }); - } - public void Dispose() { //Stop, shutdown, and destroy diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 084e4d50dc..ab347f435f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -19,6 +19,8 @@ internal class NetworkObjectReferenceTests : IDisposable { private class TestNetworkBehaviour : NetworkBehaviour { + public static bool ReceivedRPC; + public NetworkVariable TestVariable = new NetworkVariable(); public NetworkObject RpcReceivedNetworkObject; @@ -28,6 +30,7 @@ private class TestNetworkBehaviour : NetworkBehaviour [ServerRpc] public void SendReferenceServerRpc(NetworkObjectReference value) { + ReceivedRPC = true; RpcReceivedGameObject = value; RpcReceivedNetworkObject = value; } @@ -150,6 +153,60 @@ public void TestTryGet() Assert.AreEqual(networkObject, result); } + public enum NetworkObjectConstructorTypes + { + None, + NullNetworkObject, + NullGameObject + } + + [UnityTest] + public IEnumerator TestSerializeNull([Values] NetworkObjectConstructorTypes networkObjectConstructorTypes) + { + TestNetworkBehaviour.ReceivedRPC = false; + using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); + var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); + networkObjectContext.Object.Spawn(); + + switch (networkObjectConstructorTypes) + { + case NetworkObjectConstructorTypes.None: + { + testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference()); + break; + } + case NetworkObjectConstructorTypes.NullNetworkObject: + { + testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((NetworkObject)null)); + break; + } + case NetworkObjectConstructorTypes.NullGameObject: + { + testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((GameObject)null)); + break; + } + } + + + // wait for rpc completion + float t = 0; + while (!TestNetworkBehaviour.ReceivedRPC) + { + + t += Time.deltaTime; + if (t > 5f) + { + new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); + } + + yield return null; + } + + // validate + Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedNetworkObject); + Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedGameObject); + } + [UnityTest] public IEnumerator TestRpc() { @@ -305,24 +362,6 @@ public void FailSerializeGameObjectWithoutNetworkObject() }); } - [Test] - public void FailSerializeNullNetworkObject() - { - Assert.Throws(() => - { - NetworkObjectReference outReference = (NetworkObject)null; - }); - } - - [Test] - public void FailSerializeNullGameObject() - { - Assert.Throws(() => - { - NetworkObjectReference outReference = (GameObject)null; - }); - } - public void Dispose() { //Stop, shutdown, and destroy diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index c6bdde7780..085fac7824 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0-exp.5", + "version": "2.0.0-pre.2", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 5bff58f15f53cb98de607c4969a377558baa7e2f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 26 Jun 2024 14:30:39 -0500 Subject: [PATCH 061/236] fix: post 2.0.0-pre.2 fixes and updates (#2962) * chore: updating changelog and package.json (#2952) * updating changelog and package.json * adding date * fix: rotation not being 1.0 issue using rigid body for motion (#2954) * fix this fixes the issue where a quaternion could potentially be close to 1.0 but off by a very very very small amount. Under this scenario, we Normalize the quaternion prior to invoking MoveRotation. * feat: up-port of network variable traits, anticipation, serialized null references, and removal of animator component requirement (#2957) * update This contains the updates for PR-2820 (Anticipated NetworkVariable and NetworkTransform) with minor adjustments based on updates in the v2.0.0 branch. Updating the package version. Adding updated changelog entries. up-port of PR-2872 up-port of PR-2874. updating change log file * fix Even if the service specifies to synchronize, if scene management is disabled then do not synchronize. * fix Removing some of the logic to determine if session owner should synchronize a connecting client or not. * fix Fixing issue with spawning objects within OnInSceneObjectsSpawned and OnNetworkSessionSynchronized primarily on the session owner side. * update Reducing the notification down to just synchronization since in-scene notification is called on the spawn sweep. * test Adding test for preventing spawning prior to being approved. Adding tests for when spawning during NetworkBehaviour.OnInSceneObjectsSpawned or NetworkBehaviour.OnNetworkSessionSynchronized or both. * fix Up port of #2953 * update Adding change log entries. * style removing white space * update reducing GC overhead during deferred despawn * style Removing whitespaces * revert Reverting deferred spawn update. --------- Co-authored-by: Frank Luong <100299641+fluong6@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 13 ++ .../Runtime/Core/NetworkManager.cs | 21 +- .../Runtime/Core/NetworkObject.cs | 11 +- .../Messages/ClientConnectedMessage.cs | 2 +- .../Messages/ConnectionApprovedMessage.cs | 17 +- .../AnticipatedNetworkVariable.cs | 1 + .../NetworkVariable/NetworkVariable.cs | 1 + .../TypedSerializerImplementations.cs | 6 +- .../SceneManagement/NetworkSceneManager.cs | 5 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 11 + .../NetworkObjectSpawnerTest.unity | 206 ++++++++++++++++++ .../NetworkObjectSpawnerTest.unity.meta | 7 + .../Tests/Runtime/NetworkObjectSpawning.cs | 161 ++++++++++++++ .../Runtime/NetworkObjectSpawning.cs.meta | 2 + .../Support/NetworkObjectSpawnerForTests.cs | 90 ++++++++ .../NetworkObjectSpawnerForTests.cs.meta | 2 + .../ProjectSettings/EditorBuildSettings.asset | 3 + 17 files changed, 514 insertions(+), 45 deletions(-) create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity.meta create mode 100644 testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs create mode 100644 testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f21849e0fe..8d85bb4ec7 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## Unreleased + +### Added + +### Fixed + +- Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined.(#2962) +- Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962) +- Fixed issue when using a distributed authority network topology where it would allow a session owner to spawn a `NetworkObject` prior to being approved. Now, an error message is logged and the `NetworkObject` will not be spawned prior to the client being approved. (#2962) +- Fixed issue where attempting to spawn during `NetworkBehaviour.OnInSceneObjectsSpawned` and `NetworkBehaviour.OnNetworkSessionSynchronized` notifications would throw a collection modified exception. (#2962) + +### Changed + ## [2.0.0-pre.2] - 2024-06-17 ### Added diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index f7880d1c3c..95f9605729 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1250,17 +1250,8 @@ public bool StartServer() { SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); - // Notify the server that all in-scnee placed NetworkObjects are spawned at this time. - foreach (var networkObject in SpawnManager.SpawnedObjectsList) - { - networkObject.InternalInSceneNetworkObjectsSpawned(); - } - // Notify the server that everything should be synchronized/spawned at this time. - foreach (var networkObject in SpawnManager.SpawnedObjectsList) - { - networkObject.InternalNetworkSessionSynchronized(); - } + SpawnManager.NotifyNetworkObjectsSynchronized(); OnServerStarted?.Invoke(); ConnectionManager.LocalClient.IsApproved = true; return true; @@ -1407,17 +1398,9 @@ private void HostServerInitialize() } SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); - // Notify the host that all in-scnee placed NetworkObjects are spawned at this time. - foreach (var networkObject in SpawnManager.SpawnedObjectsList) - { - networkObject.InternalInSceneNetworkObjectsSpawned(); - } // Notify the host that everything should be synchronized/spawned at this time. - foreach (var networkObject in SpawnManager.SpawnedObjectsList) - { - networkObject.InternalNetworkSessionSynchronized(); - } + SpawnManager.NotifyNetworkObjectsSynchronized(); OnServerStarted?.Invoke(); OnClientStarted?.Invoke(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index b927722a73..5069eab8b3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1547,6 +1547,11 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla if (NetworkManager.DistributedAuthorityMode) { + if (NetworkManager.LocalClient == null || !NetworkManager.IsConnectedClient || !NetworkManager.ConnectionManager.LocalClient.IsApproved) + { + Debug.LogError($"Cannot spawn {name} until the client is fully connected to the session!"); + return; + } if (NetworkManager.NetworkConfig.EnableSceneManagement) { NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; @@ -2436,10 +2441,10 @@ internal bool SetNetworkVariableData(FastBufferReader reader, ulong clientId) if (NetworkManager.DistributedAuthorityMode) { var readerPosition = reader.Position; - reader.ReadValueSafe(out ushort behaviorCount); - if (behaviorCount != ChildNetworkBehaviours.Count) + reader.ReadValueSafe(out ushort behaviourCount); + if (behaviourCount != ChildNetworkBehaviours.Count) { - Debug.LogError($"Network Behavior Count Mismatch! [{readerPosition}][{reader.Position}]"); + Debug.LogError($"[{name}] Network Behavior Count Mismatch! [In: {behaviourCount} vs Local: {ChildNetworkBehaviours.Count}][StartReaderPos: {readerPosition}] CurrentReaderPos: {reader.Position}]"); return false; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index 819b632fa5..0a1b1aeeb4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -31,7 +31,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - if ((ShouldSynchronize || networkManager.CMBServiceConnection) && networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) + if (ShouldSynchronize && networkManager.NetworkConfig.EnableSceneManagement && networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) { networkManager.SceneManager.SynchronizeNetworkObjects(ClientId); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 82eef2489d..095b5097ca 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -224,10 +224,7 @@ public void Handle(ref NetworkContext context) networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); // For convenience, notify all NetworkBehaviours that synchronization is complete. - foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) - { - networkObject.InternalNetworkSessionSynchronized(); - } + networkManager.SpawnManager.NotifyNetworkObjectsSynchronized(); } else { @@ -243,13 +240,6 @@ public void Handle(ref NetworkContext context) // Spawn any in-scene placed NetworkObjects networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); - // With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself, - // we need to notify the session owner that all in-scnee placed NetworkObjects are spawned at this time. - foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) - { - networkObject.InternalInSceneNetworkObjectsSpawned(); - } - // Spawn the local player of the session owner if (networkManager.AutoSpawnPlayerPrefabClientSide) { @@ -261,10 +251,7 @@ public void Handle(ref NetworkContext context) // With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself, // we need to notify the session owner that everything should be synchronized/spawned at this time. - foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) - { - networkObject.InternalNetworkSessionSynchronized(); - } + networkManager.SpawnManager.NotifyNetworkObjectsSynchronized(); // When scene management is enabled and since the session owner is synchronizing the service (i.e. acting like host), // we need to locallyh invoke the OnClientConnected callback at this point in time. diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index 2a08293206..eb8744f6a2 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -51,6 +51,7 @@ public enum StaleDataHandling #pragma warning restore IDE0001 [Serializable] [GenerateSerializationForGenericParameter(0)] + [GenerateSerializationForType(typeof(byte))] public class AnticipatedNetworkVariable : NetworkVariableBase { [SerializeField] diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 121f7e67b2..0bb39fb0c5 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -9,6 +9,7 @@ namespace Unity.Netcode /// the unmanaged type for [Serializable] [GenerateSerializationForGenericParameter(0)] + [GenerateSerializationForType(typeof(byte))] public class NetworkVariable : NetworkVariableBase { /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs index 91d3a60b5e..7c61bbf8b1 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -680,7 +680,7 @@ public void Duplicate(in NativeHashMap value, ref NativeHashMap - /// Serializer for FixedStrings + /// Serializer for FixedStrings /// /// internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged, INativeList, IUTF8Bytes @@ -731,7 +731,7 @@ public unsafe void WriteDelta(FastBufferWriter writer, ref T value, ref T previo return; } - writer.WriteByte(0); // Flag that we're sending a delta + writer.WriteByteSafe(0); // Flag that we're sending a delta BytePacker.WriteValuePacked(writer, value.Length); writer.WriteValueSafe(changes); var ptr = value.GetUnsafePtr(); @@ -779,7 +779,7 @@ public unsafe void ReadDelta(FastBufferReader reader, ref T value) { if (changes.IsSet(i)) { - reader.ReadByte(out ptr[i]); + reader.ReadByteSafe(out ptr[i]); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index ec41e5d0e4..4d6a23b131 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2436,10 +2436,7 @@ private void HandleClientSceneEvent(uint sceneEventId) NetworkLog.LogInfo($"[Client-{NetworkManager.LocalClientId}][Scene Management Enabled] Synchronization complete!"); } // For convenience, notify all NetworkBehaviours that synchronization is complete. - foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) - { - networkObject.InternalNetworkSessionSynchronized(); - } + NetworkManager.SpawnManager.NotifyNetworkObjectsSynchronized(); if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority() && IsRestoringSession) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 0de0da4fdd..b375dc039e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1863,5 +1863,16 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) DeferredDespawnObjects.Remove(deferredObjectEntry); } } + + internal void NotifyNetworkObjectsSynchronized() + { + // Users could spawn NetworkObjects during these notifications. + // Create a separate list from the hashset to avoid list modification errors. + var spawnedObjects = SpawnedObjectsList.ToList(); + foreach (var networkObject in spawnedObjects) + { + networkObject.InternalNetworkSessionSynchronized(); + } + } } } diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity new file mode 100644 index 0000000000..ddc9c46f9f --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity @@ -0,0 +1,206 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &1633685737 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1633685739} + - component: {fileID: 1633685738} + - component: {fileID: 1633685740} + - component: {fileID: 1633685741} + m_Layer: 0 + m_Name: InSceneObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1633685738 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 3975491472 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!4 &1633685739 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1633685740 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 75c714dc47c5306488e8605e46dcd87a, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1633685741 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a915cfb2e4f748e4f9526a8bf5ee84f2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1633685739} diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity.meta new file mode 100644 index 0000000000..9406cabace --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 28ec9a0d98d9db64a9c57a79f38d6e0a +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs new file mode 100644 index 0000000000..e0cafb1334 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs @@ -0,0 +1,161 @@ +using System.Collections; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + internal class NetworkObjectSpawning : NetcodeIntegrationTest + { + private const string k_SceneToLoad = "NetworkObjectSpawnerTest"; + + private GameObject m_ObjectToSpawn; + + private bool m_CanStartServerAndClients; + + protected override int NumberOfClients => 1; + + public NetworkObjectSpawning(NetworkTopologyTypes networkTopology) : base(networkTopology) { } + + + protected override IEnumerator OnSetup() + { + NetworkObjectSpawnerForTests.Reset(); + m_CanStartServerAndClients = false; + return base.OnSetup(); + } + + protected override IEnumerator OnTearDown() + { + Object.DestroyImmediate(m_ObjectToSpawn); + m_ObjectToSpawn = null; + return base.OnTearDown(); + } + + protected override bool CanStartServerAndClients() + { + return m_CanStartServerAndClients; + } + + protected override void OnServerAndClientsCreated() + { + m_ObjectToSpawn = NetworkObjectSpawnerForTests.ObjectToSpawn = CreateNetworkObjectPrefab("TestObject"); + base.OnServerAndClientsCreated(); + } + + private void OnClientStarted() + { + if (m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority) + { + LogAssert.Expect(LogType.Error, new Regex($"Cannot spawn {m_ObjectToSpawn.name} until the client is fully connected to the session!")); + var networkObject = m_ObjectToSpawn.GetComponent(); + networkObject.NetworkManagerOwner = m_ClientNetworkManagers[0]; + networkObject.Spawn(); + m_ClientNetworkManagers[0].OnClientStarted -= OnClientStarted; + } + } + + /// + /// Validates that a NetworkObject cannot be spawned before being approved when + /// using a distributed authority network topology. + /// + [UnityTest] + public IEnumerator CannotSpawnBeforeApproved() + { + m_CanStartServerAndClients = true; + m_ClientNetworkManagers[0].OnClientStarted += OnClientStarted; + yield return StartServerAndClients(); + } + + private Scene m_SceneLoaded; + private bool m_SceneLoadCompleted; + private NetworkSceneManager.VerifySceneBeforeLoadingDelegateHandler m_ServerVerificationAction; + + public enum SynchronizeNotificationTypes + { + InSceneObjects, + Synchronized, + Both + } + + /// + /// Validates that you can spawn during and (or spawn during both) + /// + [UnityTest] + public IEnumerator SpawnOnSynchronizedNotification([Values] SynchronizeNotificationTypes synchronizeNotificationTypes) + { + m_SceneLoadCompleted = false; + m_CanStartServerAndClients = true; + NetworkObjectSpawnerForTests.SpawnAfterInSceneSynchronized = synchronizeNotificationTypes == SynchronizeNotificationTypes.InSceneObjects || synchronizeNotificationTypes == SynchronizeNotificationTypes.Both; + NetworkObjectSpawnerForTests.SpawnAfterSynchronized = synchronizeNotificationTypes == SynchronizeNotificationTypes.Synchronized || synchronizeNotificationTypes == SynchronizeNotificationTypes.Both; + NetworkObjectSpawnerForTests.OnlyAuthoritySpawns = !m_DistributedAuthority; + + if (synchronizeNotificationTypes == SynchronizeNotificationTypes.Both) + { + SceneManager.sceneLoaded += SceneLoaded; + var asyncTask = SceneManager.LoadSceneAsync(k_SceneToLoad, LoadSceneMode.Additive); + yield return WaitForConditionOrTimeOut(() => asyncTask.isDone); + AssertOnTimeout($"Failed to load scene {k_SceneToLoad}!"); + } + + yield return StartServerAndClients(); + + if (synchronizeNotificationTypes != SynchronizeNotificationTypes.Both) + { + m_ServerNetworkManager.SceneManager.OnSceneEvent += OnSceneEvent; + m_ServerNetworkManager.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); + + yield return WaitForConditionOrTimeOut(() => m_SceneLoaded.name == k_SceneToLoad && m_SceneLoaded.IsValid() && m_SceneLoaded.isLoaded && m_SceneLoadCompleted); + AssertOnTimeout($"Failed to load scene {k_SceneToLoad}!"); + + m_ServerNetworkManager.SceneManager.OnSceneEvent -= OnSceneEvent; + } + + yield return s_DefaultWaitForTick; + var hostOrSessionOwner = m_DistributedAuthority ? "Session Owner" : "Host"; + + Assert.IsTrue(NetworkObjectSpawnerForTests.SpawnedInstances.ContainsKey(NetworkManager.ServerClientId), $"[{m_NetworkTopologyType}] {hostOrSessionOwner} did not spawn any objects during notification for in-scene objects synchronized!"); + var expectedSpawnCount = synchronizeNotificationTypes == SynchronizeNotificationTypes.Both ? 2 : 1; + var authorityCount = NetworkObjectSpawnerForTests.SpawnedInstances[NetworkManager.ServerClientId].Count; + Assert.IsTrue(authorityCount == expectedSpawnCount, $"[{m_NetworkTopologyType}] {hostOrSessionOwner} only spawned {authorityCount} but was expected to spawn {expectedSpawnCount}!"); + + if (m_DistributedAuthority) + { + Assert.IsTrue(NetworkObjectSpawnerForTests.SpawnedInstances.ContainsKey(m_ClientNetworkManagers[0].LocalClientId), $"[{m_NetworkTopologyType}] Client did not spawn any objects during notification for in-scene objects synchronized!"); + var clientCount = NetworkObjectSpawnerForTests.SpawnedInstances[m_ClientNetworkManagers[0].LocalClientId].Count; + Assert.IsTrue(clientCount == expectedSpawnCount, $"[{m_NetworkTopologyType}] Client-{m_ClientNetworkManagers[0].LocalClientId} only spawned {clientCount} but was expected to spawn {expectedSpawnCount}!"); + } + + if (synchronizeNotificationTypes == SynchronizeNotificationTypes.Both) + { + SceneManager.UnloadSceneAsync(m_SceneLoaded); + } + } + + private void SceneLoaded(Scene scene, LoadSceneMode arg1) + { + if (scene.name == k_SceneToLoad) + { + m_SceneLoaded = scene; + } + } + + private void OnSceneEvent(SceneEvent sceneEvent) + { + if (sceneEvent.SceneEventType == SceneEventType.LoadComplete && sceneEvent.ClientId == m_ServerNetworkManager.LocalClientId && sceneEvent.Scene.name == k_SceneToLoad) + { + m_SceneLoaded = sceneEvent.Scene; + } + else if (sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted && sceneEvent.SceneName == k_SceneToLoad) + { + m_SceneLoadCompleted = true; + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs.meta b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs.meta new file mode 100644 index 0000000000..e1d0c1b5f2 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d287f6fbe5791cf4782fb4bc75fc85e0 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs b/testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs new file mode 100644 index 0000000000..f4102366fc --- /dev/null +++ b/testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using Unity.Netcode; +using UnityEngine; + +namespace TestProject.RuntimeTests +{ + internal class NetworkObjectSpawnerForTests : NetworkBehaviour + { + internal static Dictionary Instances = new Dictionary(); + + internal static Dictionary> SpawnedInstances = new Dictionary>(); + + internal static GameObject ObjectToSpawn; + + internal static void Reset() + { + Instances.Clear(); + SpawnedInstances.Clear(); + if (ObjectToSpawn) + { + DestroyImmediate(ObjectToSpawn); + } + ObjectToSpawn = null; + } + + internal static bool SpawnAfterInSceneSynchronized; + internal static bool SpawnAfterSynchronized; + internal static bool OnlyAuthoritySpawns; + + public override void OnNetworkSpawn() + { + if (!Instances.ContainsKey(NetworkManager.LocalClientId)) + { + Instances.Add(NetworkManager.LocalClientId, this); + } + else + { + Debug.LogError($"Already have an instance registered for Client-{NetworkManager.LocalClientId}! (did you forget to clear in teardown?)"); + } + base.OnNetworkSpawn(); + } + + protected override void OnInSceneObjectsSpawned() + { + CheckForSpawn(); + base.OnInSceneObjectsSpawned(); + } + + protected override void OnNetworkSessionSynchronized() + { + CheckForSpawn(); + base.OnNetworkSessionSynchronized(); + } + + private void CheckForSpawn() + { + if (!OnlyAuthoritySpawns || (OnlyAuthoritySpawns && ((NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) || (!NetworkManager.DistributedAuthorityMode && IsServer)))) + { + if (!NetworkManager.DistributedAuthorityMode && !IsServer) + { + SpawnRpc(); + } + else + { + Spawn(NetworkManager.LocalClientId); + } + } + } + + [Rpc(SendTo.Server)] + private void SpawnRpc(RpcParams rpcParams = default) + { + Spawn(rpcParams.Receive.SenderClientId); + } + + private void Spawn(ulong ownerId) + { + var instance = Instantiate(ObjectToSpawn); + var networkObject = instance.GetComponent(); + networkObject.SpawnWithOwnership(ownerId); + if (!SpawnedInstances.ContainsKey(ownerId)) + { + SpawnedInstances.Add(ownerId, new List()); + } + SpawnedInstances[ownerId].Add(networkObject); + } + + + } +} diff --git a/testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs.meta b/testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs.meta new file mode 100644 index 0000000000..d1f61ba582 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/Support/NetworkObjectSpawnerForTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 75c714dc47c5306488e8605e46dcd87a \ No newline at end of file diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index ea315828a3..cbd59bacd5 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -152,6 +152,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/IntegrationTestScenes/SessionSynchronize.unity guid: 468b795904b98234593ebc31bf0d578a + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/NetworkObjectSpawnerTest.unity + guid: 28ec9a0d98d9db64a9c57a79f38d6e0a m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} From 8f71c14ce4c77d482a9204a1907e7139fefef673 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 1 Jul 2024 09:47:07 -0500 Subject: [PATCH 062/236] update Send NetworkTransform updates to the service even if no clients are connected. --- .../Runtime/Components/NetworkTransform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7518ee22b8..939fe49cf0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3535,7 +3535,7 @@ internal int SerializeMessage(FastBufferWriter writer, int targetVersion) ///
private void UpdateTransformState() { - if (m_CachedNetworkManager.ShutdownInProgress || (m_CachedNetworkManager.DistributedAuthorityMode && m_CachedNetworkObject.Observers.Count - 1 == 0)) + if (m_CachedNetworkManager.ShutdownInProgress || (m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.CMBServiceConnection && m_CachedNetworkObject.Observers.Count - 1 == 0)) { return; } From d4a6dcc9de66a6ef7f8a214b5cadd9345a7feb78 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 15 Jul 2024 09:59:34 -0500 Subject: [PATCH 063/236] fix: InstantiateAndSpawn not honoring ownerClientId parameter in client-server (#2968) * fix NetworkSpawnManager.InstantiateAndSpawn should use ownerClientId when not in distributed authority mode. * fix NetworkObject.InstantiateAndSpawn should use ownerClientId when not in distributed authority mode. * test and update Updated NetworkObjectSpawning to validate against InstantiateAndSpawn. Updated ChangeLog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkObject.cs | 2 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- .../Tests/Runtime/NetworkObjectSpawning.cs | 56 +++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8d85bb4ec7..c30b7f982e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968) - Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined.(#2962) - Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962) - Fixed issue when using a distributed authority network topology where it would allow a session owner to spawn a `NetworkObject` prior to being approved. Now, an error message is logged and the `NetworkObject` will not be spawned prior to the client being approved. (#2962) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 5069eab8b3..a2749ddec4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1643,7 +1643,7 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow return null; } - ownerClientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; + ownerClientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : ownerClientId; // We only need to check for authority when running in client-server mode if (!networkManager.IsServer && !networkManager.DistributedAuthorityMode) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index b375dc039e..dd9cd2fc8b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -643,7 +643,7 @@ public NetworkObject InstantiateAndSpawn(NetworkObject networkPrefab, ulong owne return null; } - ownerClientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; + ownerClientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : ownerClientId; // We only need to check for authority when running in client-server mode if (!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) { diff --git a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs index e0cafb1334..dbf2120411 100644 --- a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs +++ b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs @@ -138,6 +138,62 @@ public IEnumerator SpawnOnSynchronizedNotification([Values] SynchronizeNotificat } } + + public enum InstantiateAndSpawnTypes + { + NetworkSpawnManager, + NetworkObject, + } + + public enum InstantiateAndSpawnContexts + { + Host, + Client, + } + + [UnityTest] + public IEnumerator InstantiateAndSpawn([Values] InstantiateAndSpawnTypes instantiateAndSpawnTypes, [Values] InstantiateAndSpawnContexts instantiateAndSpawnContexts) + { + m_CanStartServerAndClients = true; + Object.DontDestroyOnLoad(m_ObjectToSpawn); + yield return StartServerAndClients(); + var instanceNetworkObject = (NetworkObject)null; + // Setup the test relative to parameters driven context + var ownerId = instantiateAndSpawnContexts == InstantiateAndSpawnContexts.Host ? m_ServerNetworkManager.LocalClientId : m_ClientNetworkManagers[0].LocalClientId; + var spawnManager = m_ServerNetworkManager.SpawnManager; + if (m_DistributedAuthority && instantiateAndSpawnContexts == InstantiateAndSpawnContexts.Client) + { + spawnManager = m_ClientNetworkManagers[0].SpawnManager; + } + + // Spawn the NetworkObject + if (instantiateAndSpawnTypes == InstantiateAndSpawnTypes.NetworkSpawnManager) + { + Debug.Log($"Spawning {m_ObjectToSpawn.name}"); + instanceNetworkObject = spawnManager.InstantiateAndSpawn(m_ObjectToSpawn.GetComponent(), ownerId); + } + else + { + instanceNetworkObject = NetworkObject.InstantiateAndSpawn(m_ObjectToSpawn, spawnManager.NetworkManager, ownerId); + } + Assert.IsNotNull(instanceNetworkObject, $"Failed to instantiate {m_ObjectToSpawn.name}!"); + Assert.IsTrue(instanceNetworkObject.IsSpawned, $"Failed to spawn {m_ObjectToSpawn.name}!"); + Assert.IsTrue(instanceNetworkObject.OwnerClientId == ownerId, $"Invalid owner ({instanceNetworkObject.OwnerClientId} vs {ownerId})!"); + yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClientId) && s_GlobalNetworkObjects.ContainsKey(m_ServerNetworkManager.LocalClientId)); + AssertOnTimeout($"Timed out waiting for all instances to spawn!"); + yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId].ContainsKey(instanceNetworkObject.NetworkObjectId) && s_GlobalNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(instanceNetworkObject.NetworkObjectId)); + AssertOnTimeout($"Timed out waiting for all instances to spawn!"); + + if (instantiateAndSpawnContexts == InstantiateAndSpawnContexts.Host) + { + Assert.IsTrue(s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId].ContainsKey(instanceNetworkObject.NetworkObjectId), $"Client-{m_ClientNetworkManagers[0].LocalClientId} failed to spawn {instanceNetworkObject.name}!"); + } + else + { + Assert.IsTrue(s_GlobalNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(instanceNetworkObject.NetworkObjectId), $"Client-{m_ServerNetworkManager.LocalClientId} failed to spawn {instanceNetworkObject.name}!"); + } + } + private void SceneLoaded(Scene scene, LoadSceneMode arg1) { if (scene.name == k_SceneToLoad) From ee0e045a168aa25c904f0733c98ba61b726e9b9b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 15 Jul 2024 19:20:11 -0500 Subject: [PATCH 064/236] fix Send initial scenes loaded, then spawn in-scene placed NetworkObjects and then spawn in-scene placed and the local session owner player and then re-synch the session. --- .../Runtime/Messaging/Messages/ConnectionApprovedMessage.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 095b5097ca..bb3e446817 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -237,6 +237,9 @@ public void Handle(ref NetworkContext context) if (!IsRestoredSession) { + // Synchronize the service with the initial session owner's loaded scenes and spawned objects + networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); + // Spawn any in-scene placed NetworkObjects networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); From 558fc67158509b555d1cf77a984f60b3048e8359 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 16 Jul 2024 17:38:21 -0500 Subject: [PATCH 065/236] fix: scenemanager clean up ScenesLoaded after synch unloading of remaining scenes not used (#2971) * fix This resolves an issue where if you set remaining loaded scenes to be unloaded after synchronization the synchronized scenes loaded list is not updated which provides a false positive of a scene being loaded and synchronized. * fix Fixing issue with 6000.0.11f1 changes to velocity and isKinematic. * fix Missed one spot where Rigidbody2D.velocity was being used. Noticed a bug where it was using Rigidbody2D.velocity as opposed to Rigidbody2D.angularVelocity. * test adding validation test for PR fixes * update adding changelog entries. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ .../Components/NetworkRigidBodyBase.cs | 18 ++++++++++++++--- .../DefaultSceneManagerHandler.cs | 5 +++++ .../SceneManagement/NetworkSceneManager.cs | 20 +++++++++---------- .../Runtime/com.unity.netcode.runtime.asmdef | 5 +++++ .../Runtime/IntegrationTestSceneHandler.cs | 5 +++++ .../NetworkSceneManagerSeneVerification.cs | 17 ++++++++++++++++ 7 files changed, 59 insertions(+), 13 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c30b7f982e..99e0726ebe 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded. (#2971) +- Fixed issue where `Rigidbody2d` under Unity 6000.0.11f1 has breaking changes where `velocity` is now `linearVelocity` and `isKinematic` is replaced by `bodyType`. (#2971) - Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968) - Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined.(#2962) - Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 17ebaf77aa..a85f7c09bd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -177,7 +177,11 @@ public void SetLinearVelocity(Vector3 linearVelocity) { if (m_IsRigidbody2D) { +#if COM_UNITY_MODULES_PHYSICS2D_LINEAR + m_Rigidbody2D.linearVelocity = linearVelocity; +#else m_Rigidbody2D.velocity = linearVelocity; +#endif } else { @@ -197,7 +201,11 @@ public Vector3 GetLinearVelocity() { if (m_IsRigidbody2D) { +#if COM_UNITY_MODULES_PHYSICS2D_LINEAR + return m_Rigidbody2D.linearVelocity; +#else return m_Rigidbody2D.velocity; +#endif } else { @@ -238,7 +246,7 @@ public Vector3 GetAngularVelocity() { if (m_IsRigidbody2D) { - return Vector3.forward * m_Rigidbody2D.velocity; + return Vector3.forward * m_Rigidbody2D.angularVelocity; } else { @@ -481,7 +489,7 @@ public bool IsKinematic() { if (m_IsRigidbody2D) { - return m_Rigidbody2D.isKinematic; + return m_Rigidbody2D.bodyType == RigidbodyType2D.Kinematic; } else { @@ -510,7 +518,7 @@ public void SetIsKinematic(bool isKinematic) { if (m_IsRigidbody2D) { - m_Rigidbody2D.isKinematic = isKinematic; + m_Rigidbody2D.bodyType = isKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic; } else { @@ -715,7 +723,11 @@ private void ApplyFixedJoint2D(NetworkRigidbodyBase bodyToConnect, Vector3 posit if (zeroVelocity) { +#if COM_UNITY_MODULES_PHYSICS2D_LINEAR + m_Rigidbody2D.linearVelocity = Vector2.zero; +#else m_Rigidbody2D.velocity = Vector2.zero; +#endif m_Rigidbody2D.angularVelocity = 0.0f; } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 9de36e99ca..63203b142a 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -227,6 +227,11 @@ public void UnloadUnassignedScenes(NetworkManager networkManager = null) foreach (var sceneToUnload in m_ScenesToUnload) { SceneManager.UnloadSceneAsync(sceneToUnload); + // Update the ScenesLoaded when we unload scenes + if (sceneManager.ScenesLoaded.ContainsKey(sceneToUnload.handle)) + { + sceneManager.ScenesLoaded.Remove(sceneToUnload.handle); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 4d6a23b131..77b2e95560 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2406,16 +2406,6 @@ private void HandleClientSceneEvent(uint sceneEventId) NetworkManager.ConnectionManager.CreateAndSpawnPlayer(NetworkManager.LocalClientId); } - // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time - NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); - - // Notify the client that they have finished synchronizing - OnSceneEvent?.Invoke(new SceneEvent() - { - SceneEventType = sceneEventData.SceneEventType, - ClientId = NetworkManager.LocalClientId, // Client sent this to the server - }); - // Process any SceneEventType.ObjectSceneChanged messages that // were deferred while synchronizing and migrate the associated // NetworkObjects to their newly assigned scenes. @@ -2429,6 +2419,16 @@ private void HandleClientSceneEvent(uint sceneEventId) SceneManagerHandler.UnloadUnassignedScenes(NetworkManager); } + // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time + NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); + + // Notify the client that they have finished synchronizing + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventData.SceneEventType, + ClientId = NetworkManager.LocalClientId, // Client sent this to the server + }); + OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId); if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) diff --git a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef index b76ae6f223..f1ff7ec13c 100644 --- a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef +++ b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef @@ -72,6 +72,11 @@ "name": "com.unity.services.multiplayer", "expression": "0.2.0", "define": "MULTIPLAYER_SERVICES_SDK_INSTALLED" + }, + { + "name": "Unity", + "expression": "6000.0.11f1", + "define": "COM_UNITY_MODULES_PHYSICS2D_LINEAR" } ], "noEngineReferences": false diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index b414bcd58c..c444647701 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -677,6 +677,11 @@ public void UnloadUnassignedScenes(NetworkManager networkManager = null) foreach (var sceneToUnload in m_ScenesToUnload) { SceneManager.UnloadSceneAsync(sceneToUnload.Key); + // Update the ScenesLoaded when we unload scenes + if (sceneManager.ScenesLoaded.ContainsKey(sceneToUnload.Key.handle)) + { + sceneManager.ScenesLoaded.Remove(sceneToUnload.Key.handle); + } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs index b7e606bd31..9f92f4bd28 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerSeneVerification.cs @@ -361,11 +361,28 @@ public IEnumerator SceneVerifyBeforeLoadTest() } m_IsTestingVerifyScene = false; + var currentSceneName = m_CurrentScene; Assert.AreEqual(m_ServerNetworkManager.SceneManager.UnloadScene(m_CurrentScene), SceneEventProgressStatus.Started); // Now wait for scenes to unload yield return WaitForConditionOrTimeOut(ConditionPassed); AssertOnTimeout($"Timed out waiting for all clients to unload {m_CurrentSceneName}!\n{PrintFailedCondition()}"); + + // Verify that all NetworkSceneManager instances reflect the change in scenes synchronized + var scenesSynchronized = m_ServerNetworkManager.SceneManager.GetSynchronizedScenes(); + foreach (var scene in scenesSynchronized) + { + Assert.False(scene.name.Equals(currentSceneName), $"Host still thinks scene {currentSceneName} is loaded and synchronized!"); + } + + foreach (var client in m_ClientNetworkManagers) + { + scenesSynchronized = client.SceneManager.GetSynchronizedScenes(); + foreach (var scene in scenesSynchronized) + { + Assert.False(scene.name.Equals(currentSceneName), $"Client-{client.LocalClientId} still thinks scene {currentSceneName} is loaded and synchronized!"); + } + } } } } From d70f4177692fdd9c02979d7c7f809b60412dba52 Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 16 Jul 2024 20:17:16 -0400 Subject: [PATCH 066/236] feat: MPSNGM-294 patch DA network variables (#2958) * feat: MPSNGM-294 patch DA network variables * fix Fixing merge issue. Updating some methods where it was using writer as opposed to reader. * style adding whitespace. removing System.Runtime.Serialization using directive as it is not needed. * Remove unnecessary files --------- Co-authored-by: NoelStephensUnity --- .../Runtime/Core/NetworkBehaviour.cs | 7 - .../Collections/NetworkList.cs | 35 +- .../NetworkVariable/NetworkVariable.cs | 2 - .../NetworkVariable/NetworkVariableBase.cs | 46 --- .../NetworkVariable/NetworkVariableTypes.cs | 41 +++ .../NetworkVariableTypes.cs.meta | 3 + .../Serialization/FallbackSerializer.cs | 8 + .../INetworkVariableSerializer.cs | 20 +- .../NetworkVariableSerialization.cs | 51 ++- .../Serialization/TypedILPPInitializers.cs | 10 - .../TypedSerializerImplementations.cs | 345 +++++++++++++++++- .../VariableSerializationTools.cs | 54 +++ .../VariableSerializationTools.cs.meta | 3 + .../DistributedAuthorityCodecTests.cs | 26 ++ .../Tests/Runtime/NetworkVariableTests.cs | 7 +- .../AssetGroups/Built In Data.asset | 19 +- testproject/Packages/packages-lock.json | 142 ++++--- 17 files changed, 649 insertions(+), 170 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index b0935313b9..644d2651c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1130,11 +1130,6 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie // Note: In distributed authority mode, all clients can read if (NetworkVariableFields[j].CanClientRead(targetClientId)) { - if (networkManager.DistributedAuthorityMode) - { - writer.WriteValueSafe(NetworkVariableFields[j].Type); - } - if (networkManager.DistributedAuthorityMode || networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { var writePos = writer.Position; @@ -1224,8 +1219,6 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) if (networkManager.DistributedAuthorityMode) { - // Explicit setting of the NetworkVariableType is only needed for CMB Runtime - reader.ReadValueSafe(out NetworkVariableType _); reader.ReadValueSafe(out ushort size); var start_marker = reader.Position; NetworkVariableFields[j].ReadField(reader); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 0c10224b14..b84592d28a 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -24,7 +24,6 @@ public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatabl /// The callback to be invoked when the list gets changed ///
public event OnListChangedDelegate OnListChanged; - internal override NetworkVariableType Type => NetworkVariableType.NetworkList; /// /// Constructor method for @@ -94,18 +93,18 @@ public override void WriteDelta(FastBufferWriter writer) { case NetworkListEvent.EventType.Add: { - NetworkVariableSerialization.Write(writer, ref element.Value); + NetworkVariableSerialization.Serializer.Write(writer, ref element.Value); } break; case NetworkListEvent.EventType.Insert: { BytePacker.WriteValueBitPacked(writer, element.Index); - NetworkVariableSerialization.Write(writer, ref element.Value); + NetworkVariableSerialization.Serializer.Write(writer, ref element.Value); } break; case NetworkListEvent.EventType.Remove: { - NetworkVariableSerialization.Write(writer, ref element.Value); + NetworkVariableSerialization.Serializer.Write(writer, ref element.Value); } break; case NetworkListEvent.EventType.RemoveAt: @@ -116,7 +115,7 @@ public override void WriteDelta(FastBufferWriter writer) case NetworkListEvent.EventType.Value: { BytePacker.WriteValueBitPacked(writer, element.Index); - NetworkVariableSerialization.Write(writer, ref element.Value); + NetworkVariableSerialization.Serializer.Write(writer, ref element.Value); } break; case NetworkListEvent.EventType.Clear: @@ -133,13 +132,14 @@ public override void WriteField(FastBufferWriter writer) { if (m_NetworkManager.DistributedAuthorityMode) { - writer.WriteValueSafe(NetworkVariableSerialization.Type); - if (NetworkVariableSerialization.Type == CollectionItemType.Unmanaged) + SerializationTools.WriteType(writer, NetworkVariableType.NetworkList); + writer.WriteValueSafe(NetworkVariableSerialization.Serializer.Type); + if (NetworkVariableSerialization.Serializer.Type == NetworkVariableType.Unmanaged) { // Write the size of the unmanaged serialized type as it has a fixed size. This allows the CMB runtime to correctly read the unmanged type. var placeholder = new T(); var startPos = writer.Position; - NetworkVariableSerialization.Write(writer, ref placeholder); + NetworkVariableSerialization.Serializer.Write(writer, ref placeholder); var size = writer.Position - startPos; writer.Seek(startPos); BytePacker.WriteValueBitPacked(writer, size); @@ -148,7 +148,7 @@ public override void WriteField(FastBufferWriter writer) writer.WriteValueSafe((ushort)m_List.Length); for (int i = 0; i < m_List.Length; i++) { - NetworkVariableSerialization.Write(writer, ref m_List.ElementAt(i)); + NetworkVariableSerialization.Serializer.Write(writer, ref m_List.ElementAt(i)); } } @@ -158,9 +158,10 @@ public override void ReadField(FastBufferReader reader) m_List.Clear(); if (m_NetworkManager.DistributedAuthorityMode) { - // Collection item type is used by the CMB rust service, drop value here. - reader.ReadValueSafe(out CollectionItemType type); - if (type == CollectionItemType.Unmanaged) + reader.ReadValueSafe(out NetworkVariableType _); + SerializationTools.ReadType(reader, NetworkVariableSerialization.Serializer); + // Collection item type is used by the DA server, drop value here. + if (NetworkVariableSerialization.Serializer.Type == NetworkVariableType.Unmanaged) { ByteUnpacker.ReadValueBitPacked(reader, out int _); } @@ -169,7 +170,7 @@ public override void ReadField(FastBufferReader reader) for (int i = 0; i < count; i++) { var value = new T(); - NetworkVariableSerialization.Read(reader, ref value); + NetworkVariableSerialization.Serializer.Read(reader, ref value); m_List.Add(value); } } @@ -186,7 +187,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) case NetworkListEvent.EventType.Add: { var value = new T(); - NetworkVariableSerialization.Read(reader, ref value); + NetworkVariableSerialization.Serializer.Read(reader, ref value); m_List.Add(value); if (OnListChanged != null) @@ -215,7 +216,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { ByteUnpacker.ReadValueBitPacked(reader, out int index); var value = new T(); - NetworkVariableSerialization.Read(reader, ref value); + NetworkVariableSerialization.Serializer.Read(reader, ref value); if (index < m_List.Length) { @@ -252,7 +253,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) case NetworkListEvent.EventType.Remove: { var value = new T(); - NetworkVariableSerialization.Read(reader, ref value); + NetworkVariableSerialization.Serializer.Read(reader, ref value); int index = m_List.IndexOf(value); if (index == -1) { @@ -315,7 +316,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { ByteUnpacker.ReadValueBitPacked(reader, out int index); var value = new T(); - NetworkVariableSerialization.Read(reader, ref value); + NetworkVariableSerialization.Serializer.Read(reader, ref value); if (index >= m_List.Length) { throw new Exception("Shouldn't be here, index is higher than list length"); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 0bb39fb0c5..35f863e4ab 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -45,8 +45,6 @@ public override void OnInitialize() NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); } - internal override NetworkVariableType Type => NetworkVariableType.Value; - /// /// Constructor for /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 034d730658..264afd3154 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -34,9 +34,6 @@ public abstract class NetworkVariableBase : IDisposable private protected NetworkBehaviour m_NetworkBehaviour; private NetworkManager m_InternalNetworkManager; - - internal virtual NetworkVariableType Type => NetworkVariableType.Custom; - private protected NetworkManager m_NetworkManager { get @@ -327,47 +324,4 @@ public virtual void Dispose() m_InternalNetworkManager = null; } } - - /// - /// Enum representing the different types of Network Variables. - /// - public enum NetworkVariableType : byte - { - /// - /// Value - /// Used for all of the basic NetworkVariables that contain a single value - /// - Value = 0, - - /// - /// Custom - /// For any custom implemented extension of the NetworkVariableBase - /// - Custom = 1, - - /// - /// NetworkList - /// - NetworkList = 2 - } - - public enum CollectionItemType : byte - { - /// - /// For any type that is not valid inside a NetworkVariable collection - /// - Unknown = 0, - - /// - /// The following types are valid types inside of NetworkVariable collections - /// - Short = 1, - UShort = 2, - Int = 3, - UInt = 4, - Long = 5, - ULong = 6, - Unmanaged = 7, - } - } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs new file mode 100644 index 0000000000..fc34dd5566 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs @@ -0,0 +1,41 @@ +#if UNITY_EDITOR +#endif + +namespace Unity.Netcode +{ + /// + /// Enum representing the different types of Network Variables that can be sent over the network. + /// The values cannot be changed, as they are used to serialize and deserialize variables on the DA server. + /// Adding new variables should be done by adding new values to the end of the enum + /// using the next free value. + /// + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /// Add any new Variable types to this table at the END with incremented index value + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + internal enum NetworkVariableType : byte + { + /// + /// For any type that is not known at runtime + /// + Unknown = 0, + /// + /// Value + /// Used for all of the basic NetworkVariables that contain a single value + /// + Value = 1, + + /// + /// NetworkList + /// + NetworkList = 2, + + // The following types are valid types inside of NetworkVariable collections + Short = 3, + UShort = 4, + Int = 5, + UInt = 6, + Long = 7, + ULong = 8, + Unmanaged = 9, + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta new file mode 100644 index 0000000000..4206dbc962 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: df4a4005f1c842669f94a404019400ed +timeCreated: 1718292058 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs index 5e855745c0..0b1b2c10a3 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs @@ -14,6 +14,9 @@ namespace Unity.Netcode /// internal class FallbackSerializer : INetworkVariableSerializer { + public NetworkVariableType Type => NetworkVariableType.Unknown; + public bool IsDistributedAuthorityOptimized => true; + private void ThrowArgumentError() { throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); @@ -79,6 +82,11 @@ public void Duplicate(in T value, ref T duplicatedValue) } UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); } + + public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) => ThrowArgumentError(); + public void ReadDistributedAuthority(FastBufferReader reader, ref T value) => ThrowArgumentError(); + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => ThrowArgumentError(); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => ThrowArgumentError(); } // RuntimeAccessModifiersILPP will make this `public` diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs index f462e52238..571d1dc5f0 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs @@ -3,11 +3,27 @@ namespace Unity.Netcode { /// - /// Interface used by NetworkVariables to serialize them + /// Interface used by NetworkVariables to serialize them with additional information for the DA runtime /// /// /// - internal interface INetworkVariableSerializer + internal interface IDistributedAuthoritySerializer + { + /// + /// The Type tells the DA server how to parse this type. + /// The user should never be able to override this value, as it is meaningful for the DA server + /// + public NetworkVariableType Type { get; } + public bool IsDistributedAuthorityOptimized { get; } + public void WriteDistributedAuthority(FastBufferWriter writer, ref T value); + public void ReadDistributedAuthority(FastBufferReader reader, ref T value); + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value); + } + + + /// + internal interface INetworkVariableSerializer : IDistributedAuthoritySerializer { // Write has to be taken by ref here because of INetworkSerializable // Open Instance Delegates (pointers to methods without an instance attached to them) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs index ebaba37a6e..23f23dbb04 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -1,4 +1,5 @@ using System; +using UnityEngine; namespace Unity.Netcode { @@ -16,12 +17,6 @@ public static class NetworkVariableSerialization internal static bool IsDistributedAuthority => NetworkManager.IsDistributedAuthority; - /// - /// The collection item type tells the CMB server how to read the bytes of each item in the collection - /// - /// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server - internal static CollectionItemType Type = CollectionItemType.Unknown; - /// /// A callback to check if two values are equal. /// @@ -59,7 +54,18 @@ public static class NetworkVariableSerialization /// public static void Write(FastBufferWriter writer, ref T value) { - Serializer.Write(writer, ref value); + if (IsDistributedAuthority) + { + if (!Serializer.IsDistributedAuthorityOptimized) + { + Debug.Log("This variable is not optimized for use with Distributed Authority"); + } + Serializer.WriteDistributedAuthority(writer, ref value); + } + else + { + Serializer.Write(writer, ref value); + } } /// @@ -84,7 +90,14 @@ public static void Write(FastBufferWriter writer, ref T value) /// public static void Read(FastBufferReader reader, ref T value) { - Serializer.Read(reader, ref value); + if (IsDistributedAuthority) + { + Serializer.ReadDistributedAuthority(reader, ref value); + } + else + { + Serializer.Read(reader, ref value); + } } /// @@ -106,7 +119,18 @@ public static void Read(FastBufferReader reader, ref T value) /// public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) { - Serializer.WriteDelta(writer, ref value, ref previousValue); + if (IsDistributedAuthority) + { + if (!Serializer.IsDistributedAuthorityOptimized) + { + Debug.Log("This variable is not optimized for use with Distributed Authority"); + } + Serializer.WriteDeltaDistributedAuthority(writer, ref value, ref previousValue); + } + else + { + Serializer.WriteDelta(writer, ref value, ref previousValue); + } } /// @@ -131,7 +155,14 @@ public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previo /// public static void ReadDelta(FastBufferReader reader, ref T value) { - Serializer.ReadDelta(reader, ref value); + if (IsDistributedAuthority) + { + Serializer.ReadDeltaDistributedAuthority(reader, ref value); + } + else + { + Serializer.ReadDelta(reader, ref value); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs index f3e679adeb..bbe48ab16f 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs @@ -38,14 +38,6 @@ internal static void InitializeIntegerSerialization() NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; NetworkVariableSerialization.Serializer = new UlongSerializer(); NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; - - // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server - NetworkVariableSerialization.Type = CollectionItemType.Short; - NetworkVariableSerialization.Type = CollectionItemType.UShort; - NetworkVariableSerialization.Type = CollectionItemType.Int; - NetworkVariableSerialization.Type = CollectionItemType.UInt; - NetworkVariableSerialization.Type = CollectionItemType.Long; - NetworkVariableSerialization.Type = CollectionItemType.ULong; } /// @@ -55,8 +47,6 @@ internal static void InitializeIntegerSerialization() public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged { NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); - // DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server - NetworkVariableSerialization.Type = CollectionItemType.Unmanaged; } /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs index 7c61bbf8b1..a4254cffee 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -10,6 +10,23 @@ namespace Unity.Netcode /// internal class ShortSerializer : INetworkVariableSerializer { + public NetworkVariableType Type => NetworkVariableType.Short; + public bool IsDistributedAuthorityOptimized => true; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref short value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref short value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref short value, ref short previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref short value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref short value) { BytePacker.WriteValueBitPacked(writer, value); @@ -46,6 +63,22 @@ public void Duplicate(in short value, ref short duplicatedValue) /// internal class UshortSerializer : INetworkVariableSerializer { + public NetworkVariableType Type => NetworkVariableType.UShort; + public bool IsDistributedAuthorityOptimized => true; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref ushort value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref ushort value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ushort value, ref ushort previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref ushort value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref ushort value) { BytePacker.WriteValueBitPacked(writer, value); @@ -82,6 +115,22 @@ public void Duplicate(in ushort value, ref ushort duplicatedValue) /// internal class IntSerializer : INetworkVariableSerializer { + public NetworkVariableType Type => NetworkVariableType.Int; + public bool IsDistributedAuthorityOptimized => true; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref int value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref int value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref int value, ref int previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref int value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref int value) { BytePacker.WriteValueBitPacked(writer, value); @@ -118,6 +167,22 @@ public void Duplicate(in int value, ref int duplicatedValue) /// internal class UintSerializer : INetworkVariableSerializer { + public NetworkVariableType Type => NetworkVariableType.UInt; + public bool IsDistributedAuthorityOptimized => true; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref uint value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref uint value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref uint value, ref uint previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref uint value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref uint value) { BytePacker.WriteValueBitPacked(writer, value); @@ -154,6 +219,22 @@ public void Duplicate(in uint value, ref uint duplicatedValue) /// internal class LongSerializer : INetworkVariableSerializer { + public NetworkVariableType Type => NetworkVariableType.Long; + public bool IsDistributedAuthorityOptimized => true; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref long value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref long value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref long value, ref long previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref long value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref long value) { BytePacker.WriteValueBitPacked(writer, value); @@ -190,6 +271,23 @@ public void Duplicate(in long value, ref long duplicatedValue) /// internal class UlongSerializer : INetworkVariableSerializer { + public NetworkVariableType Type => NetworkVariableType.ULong; + public bool IsDistributedAuthorityOptimized => true; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref ulong value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref ulong value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ulong value, ref ulong previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref ulong value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref ulong value) { BytePacker.WriteValueBitPacked(writer, value); @@ -231,6 +329,23 @@ public void Duplicate(in ulong value, ref ulong duplicatedValue) /// internal class UnmanagedTypeSerializer : INetworkVariableSerializer where T : unmanaged { + public NetworkVariableType Type => NetworkVariableType.Unmanaged; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref T value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref T value) { writer.WriteUnmanagedSafe(value); @@ -264,6 +379,22 @@ public void Duplicate(in T value, ref T duplicatedValue) internal class ListSerializer : INetworkVariableSerializer> { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref List value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref List value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref List value, ref List previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref List value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref List value) { var isNull = value == null; @@ -350,6 +481,22 @@ public void Duplicate(in List value, ref List duplicatedValue) internal class HashSetSerializer : INetworkVariableSerializer> where T : IEquatable { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref HashSet value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref HashSet value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref HashSet value, ref HashSet previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref HashSet value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref HashSet value) { var isNull = value == null; @@ -428,6 +575,22 @@ public void Duplicate(in HashSet value, ref HashSet duplicatedValue) internal class DictionarySerializer : INetworkVariableSerializer> where TKey : IEquatable { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref Dictionary value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref Dictionary value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref Dictionary value, ref Dictionary previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref Dictionary value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref Dictionary value) { var isNull = value == null; @@ -505,6 +668,22 @@ public void Duplicate(in Dictionary value, ref Dictionary : INetworkVariableSerializer> where T : unmanaged { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteUnmanagedSafe(value); @@ -550,6 +729,22 @@ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValu #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT internal class UnmanagedListSerializer : INetworkVariableSerializer> where T : unmanaged { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteUnmanagedSafe(value); @@ -593,6 +788,23 @@ public void Duplicate(in NativeList value, ref NativeList duplicatedValue) internal class NativeHashSetSerializer : INetworkVariableSerializer> where T : unmanaged, IEquatable { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeHashSet value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashSet value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashSet value, ref NativeHashSet previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeHashSet value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref NativeHashSet value) { writer.WriteValueSafe(value); @@ -638,6 +850,23 @@ internal class NativeHashMapSerializer : INetworkVariableSerializer< where TKey : unmanaged, IEquatable where TVal : unmanaged { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeHashMap value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashMap value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashMap value, ref NativeHashMap previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeHashMap value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref NativeHashMap value) { writer.WriteValueSafe(value); @@ -685,7 +914,22 @@ public void Duplicate(in NativeHashMap value, ref NativeHashMap internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged, INativeList, IUTF8Bytes { - // The item type can only be bytes for fixedStrings, so the DA runtime doesn't need details on it + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref T value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref T value) { @@ -802,6 +1046,22 @@ public void Duplicate(in T value, ref T duplicatedValue) /// internal class FixedStringArraySerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteValueSafe(value); @@ -852,6 +1112,23 @@ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValu /// internal class FixedStringListSerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteValueSafe(value); @@ -899,6 +1176,23 @@ public void Duplicate(in NativeList value, ref NativeList duplicatedValue) /// internal class UnmanagedNetworkSerializableSerializer : INetworkVariableSerializer where T : unmanaged, INetworkSerializable { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref T value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); @@ -951,6 +1245,22 @@ public void Duplicate(in T value, ref T duplicatedValue) /// internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteNetworkSerializable(value); @@ -1001,6 +1311,23 @@ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValu /// internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList value) => Read(reader, ref value); + public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteNetworkSerializable(value); @@ -1048,6 +1375,22 @@ public void Duplicate(in NativeList value, ref NativeList duplicatedValue) /// internal class ManagedNetworkSerializableSerializer : INetworkVariableSerializer where T : class, INetworkSerializable, new() { + public NetworkVariableType Type => NetworkVariableType.Value; + public bool IsDistributedAuthorityOptimized => false; + + public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) + { + SerializationTools.WriteType(writer, Type); + Write(writer, ref value); + } + + public void ReadDistributedAuthority(FastBufferReader reader, ref T value) + { + SerializationTools.ReadType(reader, this); + Read(reader, ref value); + } + public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); + public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs new file mode 100644 index 0000000000..50ea37d1b0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs @@ -0,0 +1,54 @@ +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; + +namespace Unity.Netcode +{ + + internal static class SerializationTools + { + public delegate void WriteDelegate(FastBufferWriter writer, ref T value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteWithSize(WriteDelegate writeMethod, FastBufferWriter writer, ref T value) + { + var writePos = writer.Position; + // Note: This value can't be packed because we don't know how large it will be in advance + // we reserve space for it, then write the data, then come back and fill in the space + // to pack here, we'd have to write data to a temporary buffer and copy it in - which + // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... + // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. + writer.WriteValueSafe((ushort)0); + var startPos = writer.Position; + writeMethod(writer, ref value); + var size = writer.Position - startPos; + writer.Seek(writePos); + writer.WriteValueSafe((ushort)size); + writer.Seek(startPos + size); + } + + public delegate void ReadDelegate(FastBufferReader writer, ref T value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadWithSize(ReadDelegate readMethod, FastBufferReader reader, ref T value) + { + reader.ReadValueSafe(out ushort _); + readMethod(reader, ref value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteType(FastBufferWriter writer, NetworkVariableType type) => writer.WriteValueSafe(type); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadType(FastBufferReader reader, INetworkVariableSerializer serializer) + { + reader.ReadValueSafe(out NetworkVariableType type); + if (type != serializer.Type) + { + throw new SerializationException(); + } + } + + + } + +} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta new file mode 100644 index 0000000000..006046fa77 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 52a4ce368df54b0a8887c08f3402bcd3 +timeCreated: 1718300602 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 843dfd364e..19ada2181e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -221,6 +221,32 @@ public IEnumerator NetworkVariableDelta() yield return SendMessage(ref message); } + [UnityTest] + public IEnumerator NetworkListDelta_WithValueUpdate() + { + var networkObj = CreateNetworkObjectPrefab("TestObject"); + networkObj.AddComponent(); + var instance = SpawnObject(networkObj, Client); + yield return m_ClientCodecHook.WaitForMessageReceived(); + var component = instance.GetComponent(); + + component.MyNetworkList.Add(5); + yield return m_ClientCodecHook.WaitForMessageReceived(); + component.MyNetworkList.Add(6); + component.MyNetworkList.Add(7); + yield return m_ClientCodecHook.WaitForMessageReceived(); + component.MyNetworkList.Insert(1, 8); + yield return m_ClientCodecHook.WaitForMessageReceived(); + component.MyNetworkList.Insert(8, 11); + yield return m_ClientCodecHook.WaitForMessageReceived(); + component.MyNetworkList.Remove(6); + yield return m_ClientCodecHook.WaitForMessageReceived(); + component.MyNetworkList.RemoveAt(2); + yield return m_ClientCodecHook.WaitForMessageReceived(); + component.MyNetworkList.Clear(); + yield return m_ClientCodecHook.WaitForMessageReceived(); + } + [UnityTest] public IEnumerator NotAuthorityRpc() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs index e59fad1131..08273957cc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs @@ -1053,7 +1053,8 @@ public void TestUnsupportedManagedTypesThrowExceptions() } [Test] - public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions() + // Exceptions should be thrown in DA mode with UserNetworkVariableSerialization + public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptionsInClientServerMode() { var variable = new NetworkVariable(); UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out string value) => @@ -1079,6 +1080,10 @@ public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions variable.ReadField(reader); Assert.AreEqual("012345", variable.Value); } + catch (Exception) + { + Assert.True(NetworkVariableSerialization>.IsDistributedAuthority); + } finally { UserNetworkVariableSerialization.ReadValue = null; diff --git a/testproject/Assets/AddressableAssetsData/AssetGroups/Built In Data.asset b/testproject/Assets/AddressableAssetsData/AssetGroups/Built In Data.asset index 0022b89409..0b67bb4c6d 100644 --- a/testproject/Assets/AddressableAssetsData/AssetGroups/Built In Data.asset +++ b/testproject/Assets/AddressableAssetsData/AssetGroups/Built In Data.asset @@ -13,24 +13,9 @@ MonoBehaviour: m_Name: Built In Data m_EditorClassIdentifier: m_GroupName: Built In Data - m_Data: - m_SerializedData: [] m_GUID: 42bcc811e41b9f54baba926c243b7608 - m_SerializeEntries: - - m_GUID: Resources - m_Address: Resources - m_ReadOnly: 1 - m_SerializedLabels: [] - m_MainAsset: {fileID: 0} - m_TargetAsset: {fileID: 0} - - m_GUID: EditorSceneList - m_Address: EditorSceneList - m_ReadOnly: 1 - m_SerializedLabels: [] - m_MainAsset: {fileID: 0} - m_TargetAsset: {fileID: 0} + m_SerializeEntries: [] m_ReadOnly: 1 m_Settings: {fileID: 11400000, guid: 75e5cd8b6bfca5d49a5818e70d71b64d, type: 2} m_SchemaSet: - m_Schemas: - - {fileID: 11400000, guid: 51fa2527519429944b6f26066c7050ff, type: 2} + m_Schemas: [] diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 5c50733307..7b289e9221 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,21 +1,22 @@ { "dependencies": { "com.unity.addressables": { - "version": "1.21.9", + "version": "2.0.8", "depth": 0, "source": "registry", "dependencies": { - "com.unity.scriptablebuildpipeline": "1.21.3", + "com.unity.scriptablebuildpipeline": "2.1.2", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0" + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.profiling.core": "1.0.2" }, "url": "https://packages.unity.com" }, "com.unity.ai.navigation": { - "version": "1.1.1", + "version": "2.0.0", "depth": 0, "source": "registry", "dependencies": { @@ -24,42 +25,43 @@ "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.6.6", + "version": "1.8.13", "depth": 2, "source": "registry", "dependencies": { - "com.unity.mathematics": "1.2.1" + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "1.17.7", + "version": "2.3.1", "depth": 0, "source": "registry", - "dependencies": { - "com.unity.services.core": "1.0.1" - }, + "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "1.2.4", + "version": "2.4.0", "depth": 2, "source": "registry", "dependencies": { - "com.unity.burst": "1.6.6", - "com.unity.test-framework": "1.1.31" + "com.unity.burst": "1.8.12", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.test-framework": "1.4.3", + "com.unity.test-framework.performance": "3.0.3" }, "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { - "version": "2.0.3", + "version": "2.0.5", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.18", + "version": "3.0.28", "depth": 0, "source": "registry", "dependencies": { @@ -68,7 +70,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.17", + "version": "2.0.22", "depth": 0, "source": "registry", "dependencies": { @@ -77,7 +79,7 @@ "url": "https://packages.unity.com" }, "com.unity.mathematics": { - "version": "1.2.6", + "version": "1.3.2", "depth": 0, "source": "registry", "dependencies": {}, @@ -88,19 +90,19 @@ "depth": 0, "source": "local", "dependencies": { - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.3.4" + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.transport": "2.2.1" } }, "com.unity.nuget.mono-cecil": { - "version": "1.10.1", + "version": "1.11.4", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.nuget.newtonsoft-json": { - "version": "3.0.2", + "version": "3.2.1", "depth": 1, "source": "registry", "dependencies": {}, @@ -115,42 +117,49 @@ }, "url": "https://packages.unity.com" }, + "com.unity.profiling.core": { + "version": "1.0.2", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.scriptablebuildpipeline": { - "version": "1.21.3", + "version": "2.1.2", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.services.authentication": { - "version": "2.4.0", + "version": "3.3.1", "depth": 0, "source": "registry", "dependencies": { - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.services.core": "1.7.0", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.services.core": "1.12.5", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.ugui": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.core": { - "version": "1.8.1", + "version": "1.12.5", "depth": 0, "source": "registry", "dependencies": { "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.modules.androidjni": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.qos": { - "version": "1.1.0", + "version": "1.3.0", "depth": 1, "source": "registry", "dependencies": { - "com.unity.services.core": "1.4.0", + "com.unity.services.core": "1.12.4", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.services.authentication": "2.0.0", @@ -176,8 +185,24 @@ }, "url": "https://packages.unity.com" }, + "com.unity.sysroot": { + "version": "2.0.7", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.sysroot.linux-x86_64": { + "version": "2.0.6", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7" + }, + "url": "https://packages.unity.com" + }, "com.unity.test-framework": { - "version": "1.3.3", + "version": "1.4.4", "depth": 0, "source": "registry", "dependencies": { @@ -188,49 +213,50 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework.performance": { - "version": "2.8.1-preview", + "version": "3.0.3", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.0", + "com.unity.test-framework": "1.1.31", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, - "com.unity.textmeshpro": { - "version": "3.0.6", + "com.unity.timeline": { + "version": "1.8.6", "depth": 0, "source": "registry", "dependencies": { - "com.unity.ugui": "1.0.0" + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" }, - "com.unity.timeline": { - "version": "1.8.2", + "com.unity.toolchain.win-x86_64-linux-x86_64": { + "version": "2.0.6", "depth": 0, "source": "registry", "dependencies": { - "com.unity.modules.director": "1.0.0", - "com.unity.modules.animation": "1.0.0", - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.particlesystem": "1.0.0" + "com.unity.sysroot": "2.0.7", + "com.unity.sysroot.linux-x86_64": "2.0.6" }, "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "1.3.4", + "version": "2.2.1", "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "1.2.4", - "com.unity.burst": "1.6.6", - "com.unity.mathematics": "1.2.6" + "com.unity.collections": "2.2.1", + "com.unity.burst": "1.8.8", + "com.unity.mathematics": "1.3.1" }, "url": "https://packages.unity.com" }, "com.unity.ugui": { - "version": "1.0.0", + "version": "2.0.0", "depth": 0, "source": "builtin", "dependencies": { @@ -238,6 +264,12 @@ "com.unity.modules.imgui": "1.0.0" } }, + "com.unity.modules.accessibility": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.ai": { "version": "1.0.0", "depth": 0, @@ -285,6 +317,12 @@ "com.unity.modules.animation": "1.0.0" } }, + "com.unity.modules.hierarchycore": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.imageconversion": { "version": "1.0.0", "depth": 0, @@ -374,17 +412,7 @@ "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.uielementsnative": "1.0.0" - } - }, - "com.unity.modules.uielementsnative": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" + "com.unity.modules.hierarchycore": "1.0.0" } }, "com.unity.modules.umbra": { From 5b93ce60e05be292ca98901846c9aa310f870ad2 Mon Sep 17 00:00:00 2001 From: Dominick Date: Thu, 18 Jul 2024 07:36:10 -0600 Subject: [PATCH 067/236] chore: protected network manager (#2975) * chore: make UnityTransport.NetworkManager protected * use m_NetworkManager * remove comments * copy/paste --- .../Runtime/Transports/UTP/UnityTransport.cs | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index f3d036b751..513c3ce6ad 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -450,7 +450,10 @@ private struct PacketLossCache private RelayServerData m_RelayServerData; - internal NetworkManager NetworkManager; + /// + /// NetworkManager associated to this transport instance + /// + protected NetworkManager m_NetworkManager; private IRealTimeProvider m_RealTimeProvider; @@ -795,10 +798,10 @@ private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue) } var mtu = 0; - if (NetworkManager) + if (m_NetworkManager) { - var ngoClientId = NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId); - mtu = NetworkManager.GetPeerMTU(ngoClientId); + var ngoClientId = m_NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId); + mtu = m_NetworkManager.GetPeerMTU(ngoClientId); } new SendBatchedMessagesJob @@ -947,7 +950,7 @@ private void Update() } #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - if (NetworkManager) + if (m_NetworkManager) { ExtractNetworkMetrics(); } @@ -963,16 +966,16 @@ private void OnDestroy() #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 private void ExtractNetworkMetrics() { - if (NetworkManager.IsServer) + if (m_NetworkManager.IsServer) { - var ngoConnectionIds = NetworkManager.ConnectedClients.Keys; + var ngoConnectionIds = m_NetworkManager.ConnectedClients.Keys; foreach (var ngoConnectionId in ngoConnectionIds) { - if (ngoConnectionId == 0 && NetworkManager.IsHost) + if (ngoConnectionId == 0 && m_NetworkManager.IsHost) { continue; } - var transportClientId = NetworkManager.ConnectionManager.ClientIdToTransportId(ngoConnectionId); + var transportClientId = m_NetworkManager.ConnectionManager.ClientIdToTransportId(ngoConnectionId); ExtractNetworkMetricsForClient(transportClientId); } } @@ -992,10 +995,10 @@ private void ExtractNetworkMetricsForClient(ulong transportClientId) ExtractNetworkMetricsFromPipeline(m_UnreliableSequencedFragmentedPipeline, networkConnection); ExtractNetworkMetricsFromPipeline(m_ReliableSequencedPipeline, networkConnection); - var rttValue = NetworkManager.IsServer ? 0 : ExtractRtt(networkConnection); + var rttValue = m_NetworkManager.IsServer ? 0 : ExtractRtt(networkConnection); NetworkMetrics.UpdateRttToServer(rttValue); - var packetLoss = NetworkManager.IsServer ? 0 : ExtractPacketLoss(networkConnection); + var packetLoss = m_NetworkManager.IsServer ? 0 : ExtractPacketLoss(networkConnection); NetworkMetrics.UpdatePacketLoss(packetLoss); } @@ -1199,9 +1202,9 @@ public override ulong GetCurrentRtt(ulong clientId) // use the transport client ID) or from a user (which will be using the NGO client ID). // So we just try both cases (ExtractRtt returns 0 for invalid connections). - if (NetworkManager != null) + if (m_NetworkManager != null) { - var transportId = NetworkManager.ConnectionManager.ClientIdToTransportId(clientId); + var transportId = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId); var rtt = ExtractRtt(ParseClientId(transportId)); if (rtt > 0) @@ -1221,14 +1224,14 @@ public override void Initialize(NetworkManager networkManager = null) { Debug.Assert(sizeof(ulong) == UnsafeUtility.SizeOf(), "Netcode connection id size does not match UTP connection id size"); - NetworkManager = networkManager; + m_NetworkManager = networkManager; - if (NetworkManager && NetworkManager.PortOverride.Overidden) + if (m_NetworkManager && m_NetworkManager.PortOverride.Overidden) { - ConnectionData.Port = NetworkManager.PortOverride.Value; + ConnectionData.Port = m_NetworkManager.PortOverride.Value; } - m_RealTimeProvider = NetworkManager ? NetworkManager.RealTimeProvider : new RealTimeProvider(); + m_RealTimeProvider = m_NetworkManager ? m_NetworkManager.RealTimeProvider : new RealTimeProvider(); m_NetworkSettings = new NetworkSettings(Allocator.Persistent); @@ -1322,7 +1325,7 @@ public override void Send(ulong clientId, ArraySegment payload, NetworkDel // provide any reliability guarantees anymore. Disconnect the client since at // this point they're bound to become desynchronized. - var ngoClientId = NetworkManager?.ConnectionManager.TransportIdToClientId(clientId) ?? clientId; + var ngoClientId = m_NetworkManager?.ConnectionManager.TransportIdToClientId(clientId) ?? clientId; Debug.LogError($"Couldn't add payload of size {payload.Count} to reliable send queue. " + $"Closing connection {ngoClientId} as reliability guarantees can't be maintained."); @@ -1479,7 +1482,7 @@ private void ConfigureSimulatorForUtp1() protected override NetworkTopologyTypes OnCurrentTopology() { - return NetworkManager != null ? NetworkManager.NetworkConfig.NetworkTopology : NetworkTopologyTypes.ClientServer; + return m_NetworkManager != null ? m_NetworkManager.NetworkConfig.NetworkTopology : NetworkTopologyTypes.ClientServer; } private string m_ServerPrivateKey; @@ -1555,7 +1558,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); #if UNITY_WEBGL && !UNITY_EDITOR - if (NetworkManager.IsServer && m_ProtocolType != ProtocolType.RelayUnityTransport) + if (m_NetworkManager.IsServer && m_ProtocolType != ProtocolType.RelayUnityTransport) { throw new Exception("WebGL as a server is not supported by Unity Transport, outside the Editor."); } @@ -1577,7 +1580,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } else { - if (NetworkManager.IsServer) + if (m_NetworkManager.IsServer) { if (string.IsNullOrEmpty(m_ServerCertificate) || string.IsNullOrEmpty(m_ServerPrivateKey)) { From 7ea9f9df36a5e222a094ecebc05e823fd94ebdc2 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 18 Jul 2024 10:14:54 -0400 Subject: [PATCH 068/236] fix: Regression between comb-server and NGO network variables (#2974) * fix: Regression between comb-server and NGO network variables * Ensure read logic is mirrored * Ensure read logic is fully mirrored * fix and update Removing the read variable type from the typed serializers. Did a little legacy code (POC DA work) clean up in the WriteNetworkVariableData and SetNetworkVariableData methods. Also added additional information in the error logging to more easily track down where any issues might be occurring. * fix length safety needed to be moved into a nested if. --------- Co-authored-by: NoelStephensUnity --- .../Runtime/Core/NetworkBehaviour.cs | 96 +++++++++++-------- .../Collections/NetworkList.cs | 3 +- .../NetworkVariable/NetworkVariable.cs | 2 + .../NetworkVariable/NetworkVariableBase.cs | 4 +- .../NetworkVariable/NetworkVariableTypes.cs | 25 +++-- .../TypedSerializerImplementations.cs | 42 -------- .../DistributedAuthorityCodecTests.cs | 16 ++++ 7 files changed, 91 insertions(+), 97 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 644d2651c3..26d3da6bd3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1112,26 +1112,38 @@ internal void MarkVariablesDirty(bool dirty) /// internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) { + // Create any values that require accessing the NetworkManager locally (it is expensive to access it in NetworkBehaviour) var networkManager = NetworkManager; - if (networkManager.DistributedAuthorityMode) + var distributedAuthority = networkManager.DistributedAuthorityMode; + var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; + + // Always write the NetworkVariable count even if zero for distributed authority (used by comb server) + if (distributedAuthority) { writer.WriteValueSafe((ushort)NetworkVariableFields.Count); } + // Exit early if there are no NetworkVariables if (NetworkVariableFields.Count == 0) { return; } - // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. - // Worth either merging or more cleanly separating these codepaths. for (int j = 0; j < NetworkVariableFields.Count; j++) { - // Note: In distributed authority mode, all clients can read + // Client-Server: Try to write values only for clients that have read permissions. + // Distributed Authority: All clients have read permissions, always try to write the value. if (NetworkVariableFields[j].CanClientRead(targetClientId)) { - if (networkManager.DistributedAuthorityMode || networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + // Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode + if (ensureLengthSafety || distributedAuthority) { + // Write the type being serialized for distributed authority (only for comb-server) + if (distributedAuthority) + { + writer.WriteValueSafe(NetworkVariableFields[j].Type); + } + var writePos = writer.Position; // Note: This value can't be packed because we don't know how large it will be in advance // we reserve space for it, then write the data, then come back and fill in the space @@ -1143,18 +1155,19 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie NetworkVariableFields[j].WriteField(writer); var size = writer.Position - startPos; writer.Seek(writePos); + // Write the NetworkVariable value writer.WriteValueSafe((ushort)size); writer.Seek(startPos + size); } - else + else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology { + // Write the NetworkVariable value NetworkVariableFields[j].WriteField(writer); } } - else + else if (ensureLengthSafety) { - // Only if EnsureNetworkVariableLengthSafety, otherwise just skip - if (networkManager.DistributedAuthorityMode || networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + // Client-Server Only: If the client cannot read this field, then skip it but write a 0 for this NetworkVariable's position { writer.WriteValueSafe((ushort)0); } @@ -1172,73 +1185,78 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie /// internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { + // Stack cache any values that requires accessing the NetworkManager (it is expensive to access it in NetworkBehaviour) var networkManager = NetworkManager; - if (networkManager.DistributedAuthorityMode) + var distributedAuthority = networkManager.DistributedAuthorityMode; + var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; + + // Always read the NetworkVariable count when in distributed authority (sanity check if comb-server matches what client has locally) + if (distributedAuthority) { reader.ReadValueSafe(out ushort variableCount); if (variableCount != NetworkVariableFields.Count) { - Debug.LogError("NetworkVariable count mismatch."); + Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}] NetworkVariable count mismatch! (Read: {variableCount} vs. Expected: {NetworkVariableFields.Count})"); return; } } + // Exit early if nothing else to read if (NetworkVariableFields.Count == 0) { return; } - // DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety. - // Worth either merging or more cleanly separating these codepaths. for (int j = 0; j < NetworkVariableFields.Count; j++) { var varSize = (ushort)0; var readStartPos = 0; - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + // Client-Server: Clients that only have read permissions will try to read the value + // Distributed Authority: All clients have read permissions, always try to read the value + if (NetworkVariableFields[j].CanClientRead(clientId)) { - reader.ReadValueSafe(out varSize); - if (varSize == 0) + if (ensureLengthSafety || distributedAuthority) { - continue; + // Read the type being serialized and discard it (for now) when in a distributed authority network topology (only used by comb-server) + if (distributedAuthority) + { + reader.ReadValueSafe(out NetworkVariableType _); + } + + reader.ReadValueSafe(out varSize); + if (varSize == 0) + { + Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected non-zero size readable NetworkVariable! (Skipping)"); + continue; + } + readStartPos = reader.Position; } - readStartPos = reader.Position; } - else // If the client cannot read this field, then skip it - if (!NetworkVariableFields[j].CanClientRead(clientId)) + else // Client-Server Only: If the client cannot read this field, then skip it { - if (networkManager.DistributedAuthorityMode) + // If skipping and length safety, then fill in a 0 size for this one spot + if (ensureLengthSafety) { reader.ReadValueSafe(out ushort size); if (size != 0) { - Debug.LogError("Expected zero size"); + Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected zero size for non-readable NetworkVariable when EnsureNetworkVariableLengthSafety is enabled! (Skipping)"); } } continue; } - if (networkManager.DistributedAuthorityMode) - { - reader.ReadValueSafe(out ushort size); - var start_marker = reader.Position; - NetworkVariableFields[j].ReadField(reader); - if (reader.Position - start_marker != size) - { - Debug.LogError("Mismatched network variable size"); - } - } - else - { - NetworkVariableFields[j].ReadField(reader); - } + // Read the NetworkVarible value + NetworkVariableFields[j].ReadField(reader); - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + // When EnsureNetworkVariableLengthSafety or DistributedAuthorityMode always do a bounds check + if (ensureLengthSafety || distributedAuthority) { if (reader.Position > (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"Var data read too far. {reader.Position - (readStartPos + varSize)} bytes."); + NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too big. {reader.Position - (readStartPos + varSize)} bytes."); } reader.Seek(readStartPos + varSize); @@ -1247,7 +1265,7 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - reader.Position} bytes."); + NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too small. {(readStartPos + varSize) - reader.Position} bytes."); } reader.Seek(readStartPos + varSize); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index b84592d28a..7a79749cea 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -24,6 +24,7 @@ public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatabl /// The callback to be invoked when the list gets changed /// public event OnListChangedDelegate OnListChanged; + internal override NetworkVariableType Type => NetworkVariableType.NetworkList; /// /// Constructor method for @@ -132,7 +133,6 @@ public override void WriteField(FastBufferWriter writer) { if (m_NetworkManager.DistributedAuthorityMode) { - SerializationTools.WriteType(writer, NetworkVariableType.NetworkList); writer.WriteValueSafe(NetworkVariableSerialization.Serializer.Type); if (NetworkVariableSerialization.Serializer.Type == NetworkVariableType.Unmanaged) { @@ -158,7 +158,6 @@ public override void ReadField(FastBufferReader reader) m_List.Clear(); if (m_NetworkManager.DistributedAuthorityMode) { - reader.ReadValueSafe(out NetworkVariableType _); SerializationTools.ReadType(reader, NetworkVariableSerialization.Serializer); // Collection item type is used by the DA server, drop value here. if (NetworkVariableSerialization.Serializer.Type == NetworkVariableType.Unmanaged) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 35f863e4ab..0bb39fb0c5 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -45,6 +45,8 @@ public override void OnInitialize() NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); } + internal override NetworkVariableType Type => NetworkVariableType.Value; + /// /// Constructor for /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 264afd3154..3431c502c7 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -34,6 +34,9 @@ public abstract class NetworkVariableBase : IDisposable private protected NetworkBehaviour m_NetworkBehaviour; private NetworkManager m_InternalNetworkManager; + + internal virtual NetworkVariableType Type => NetworkVariableType.Unknown; + private protected NetworkManager m_NetworkManager { get @@ -308,7 +311,6 @@ internal ulong OwnerClientId() /// /// The stream to read the state from public abstract void ReadField(FastBufferReader reader); - /// /// Reads delta from the reader and applies them to the internal value /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs index fc34dd5566..d8b6912ba3 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs @@ -14,28 +14,27 @@ namespace Unity.Netcode /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! internal enum NetworkVariableType : byte { - /// - /// For any type that is not known at runtime - /// - Unknown = 0, /// /// Value /// Used for all of the basic NetworkVariables that contain a single value /// - Value = 1, - + Value = 0, + /// + /// For any type that is not known at runtime + /// + Unknown = 1, /// /// NetworkList /// NetworkList = 2, // The following types are valid types inside of NetworkVariable collections - Short = 3, - UShort = 4, - Int = 5, - UInt = 6, - Long = 7, - ULong = 8, - Unmanaged = 9, + Short = 11, + UShort = 12, + Int = 13, + UInt = 14, + Long = 15, + ULong = 16, + Unmanaged = 17, } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs index a4254cffee..180f9e9e27 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -15,13 +15,11 @@ internal class ShortSerializer : INetworkVariableSerializer public void WriteDistributedAuthority(FastBufferWriter writer, ref short value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref short value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref short value, ref short previousValue) => Write(writer, ref value); @@ -68,13 +66,11 @@ internal class UshortSerializer : INetworkVariableSerializer public void WriteDistributedAuthority(FastBufferWriter writer, ref ushort value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref ushort value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ushort value, ref ushort previousValue) => Write(writer, ref value); @@ -120,13 +116,11 @@ internal class IntSerializer : INetworkVariableSerializer public void WriteDistributedAuthority(FastBufferWriter writer, ref int value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref int value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref int value, ref int previousValue) => Write(writer, ref value); @@ -172,13 +166,11 @@ internal class UintSerializer : INetworkVariableSerializer public void WriteDistributedAuthority(FastBufferWriter writer, ref uint value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref uint value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref uint value, ref uint previousValue) => Write(writer, ref value); @@ -224,13 +216,11 @@ internal class LongSerializer : INetworkVariableSerializer public void WriteDistributedAuthority(FastBufferWriter writer, ref long value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref long value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref long value, ref long previousValue) => Write(writer, ref value); @@ -276,13 +266,11 @@ internal class UlongSerializer : INetworkVariableSerializer public void WriteDistributedAuthority(FastBufferWriter writer, ref ulong value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref ulong value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ulong value, ref ulong previousValue) => Write(writer, ref value); @@ -334,13 +322,11 @@ internal class UnmanagedTypeSerializer : INetworkVariableSerializer where public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref T value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); @@ -384,13 +370,11 @@ internal class ListSerializer : INetworkVariableSerializer> public void WriteDistributedAuthority(FastBufferWriter writer, ref List value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref List value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref List value, ref List previousValue) => Write(writer, ref value); @@ -486,13 +470,11 @@ internal class HashSetSerializer : INetworkVariableSerializer> whe public void WriteDistributedAuthority(FastBufferWriter writer, ref HashSet value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref HashSet value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref HashSet value, ref HashSet previousValue) => Write(writer, ref value); @@ -580,13 +562,11 @@ internal class DictionarySerializer : INetworkVariableSerializer value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref Dictionary value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref Dictionary value, ref Dictionary previousValue) => Write(writer, ref value); @@ -673,13 +653,11 @@ internal class UnmanagedArraySerializer : INetworkVariableSerializer value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); @@ -734,13 +712,11 @@ internal class UnmanagedListSerializer : INetworkVariableSerializer value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); @@ -793,13 +769,11 @@ internal class NativeHashSetSerializer : INetworkVariableSerializer value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashSet value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashSet value, ref NativeHashSet previousValue) => Write(writer, ref value); @@ -855,13 +829,11 @@ internal class NativeHashMapSerializer : INetworkVariableSerializer< public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeHashMap value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashMap value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashMap value, ref NativeHashMap previousValue) => Write(writer, ref value); @@ -919,13 +891,11 @@ internal class FixedStringSerializer : INetworkVariableSerializer where T public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref T value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); @@ -1051,13 +1021,11 @@ internal class FixedStringArraySerializer : INetworkVariableSerializer value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); @@ -1117,13 +1085,11 @@ internal class FixedStringListSerializer : INetworkVariableSerializer value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); @@ -1181,13 +1147,11 @@ internal class UnmanagedNetworkSerializableSerializer : INetworkVariableSeria public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref T value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); @@ -1250,13 +1214,11 @@ internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariable public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); @@ -1316,13 +1278,11 @@ internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableS public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); @@ -1380,13 +1340,11 @@ public void Duplicate(in NativeList value, ref NativeList duplicatedValue) public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) { - SerializationTools.WriteType(writer, Type); Write(writer, ref value); } public void ReadDistributedAuthority(FastBufferReader reader, ref T value) { - SerializationTools.ReadType(reader, this); Read(reader, ref value); } public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 19ada2181e..42934e1800 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -38,6 +38,7 @@ internal class DistributedAuthorityCodecTests : NetcodeIntegrationTest internal class TestNetworkComponent : NetworkBehaviour { public NetworkList MyNetworkList = new NetworkList(new List { 1, 2, 3 }); + public NetworkVariable MyNetworkVar = new NetworkVariable(3); [Rpc(SendTo.NotAuthority)] public void TestNotAuthorityRpc(byte[] _) @@ -221,6 +222,21 @@ public IEnumerator NetworkVariableDelta() yield return SendMessage(ref message); } + [UnityTest] + public IEnumerator NetworkVariableDelta_WithValueUpdate() + { + var networkObj = CreateNetworkObjectPrefab("TestObject"); + networkObj.AddComponent(); + var instance = SpawnObject(networkObj, Client); + yield return m_ClientCodecHook.WaitForMessageReceived(); + var component = instance.GetComponent(); + + var newValue = 5; + component.MyNetworkVar.Value = newValue; + yield return m_ClientCodecHook.WaitForMessageReceived(); + Assert.AreEqual(newValue, component.MyNetworkVar.Value); + } + [UnityTest] public IEnumerator NetworkListDelta_WithValueUpdate() { From 9c927dfc2fd3a5e48d45f619f840abadff1c99a8 Mon Sep 17 00:00:00 2001 From: Amy Reeve Date: Thu, 18 Jul 2024 15:16:03 +0100 Subject: [PATCH 069/236] fix: Fixing API doc issues (#2976) * Fixing APi doc issues * Adding a list element --- .../Runtime/Components/HalfVector3.cs | 2 +- .../Runtime/Components/HalfVector4.cs | 2 +- .../BufferedLinearInterpolator.cs | 2 +- .../Runtime/Core/NetworkBehaviour.cs | 106 +++++++++--------- .../Runtime/Core/NetworkObject.cs | 10 +- .../SceneManagement/NetworkSceneManager.cs | 1 + .../Runtime/NetcodeIntegrationTestHelpers.cs | 2 +- .../Runtime/NetworkVariableHelper.cs | 10 +- 8 files changed, 70 insertions(+), 65 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs index 679496452c..8bba3d4170 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.Components /// Half float precision . ///
/// - /// The Vector3T values are half float values returned by for each + /// The Vector3T<ushort> values are half float values returned by for each /// individual axis and the 16 bits of the half float are stored as values since C# does not have /// a half float type. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs index 35ba13859a..1ff2b09076 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs @@ -8,7 +8,7 @@ namespace Unity.Netcode.Components /// Half Precision that can also be used to convert a to half precision. ///
/// - /// The Vector4T values are half float values returned by for each + /// The Vector4T<ushort> values are half float values returned by for each /// individual axis and the 16 bits of the half float are stored as values since C# does not have /// a half float type. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 543917b41e..ef5ec09d86 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -5,7 +5,7 @@ namespace Unity.Netcode { /// - /// Solves for incoming values that are jittered + /// Solves for incoming values that are jittered. /// Partially solves for message loss. Unclamped lerping helps hide this, but not completely /// /// The type of interpolated value diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 26d3da6bd3..83c60ca027 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -15,7 +15,7 @@ public RpcException(string message) : base(message) } /// - /// The base class to override to write network code. Inherits MonoBehaviour + /// The base class to override to write network code. Inherits MonoBehaviour. /// public abstract class NetworkBehaviour : MonoBehaviour { @@ -410,8 +410,8 @@ internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ul } /// - /// Gets the NetworkManager that owns this NetworkBehaviour instance - /// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized + /// Gets the NetworkManager that owns this NetworkBehaviour instance. + /// See `NetworkObject` note for how there is a chicken/egg problem when not initialized. /// public NetworkManager NetworkManager { @@ -439,38 +439,40 @@ public NetworkManager NetworkManager /// , /// , /// , and - /// + /// . ///
#pragma warning restore IDE0001 public RpcTarget RpcTarget => NetworkManager.RpcTarget; /// - /// If a NetworkObject is assigned, it will return whether or not this NetworkObject - /// is the local player object. If no NetworkObject is assigned it will always return false. + /// If a NetworkObject is assigned, returns whether the NetworkObject + /// is the local player object. If no NetworkObject is assigned, returns false. /// public bool IsLocalPlayer { get; private set; } /// - /// Gets if the object is owned by the local player or if the object is the local player object + /// Gets whether the object is owned by the local player or if the object is the local player object. /// public bool IsOwner { get; internal set; } /// - /// Gets if we are executing as server + /// Gets whether executing as a server. /// public bool IsServer { get; private set; } /// - /// Determines if the local client has authority over the associated NetworkObject - /// Client-Server: This will return true if IsServer or IsHost - /// Distributed Authority: This will return true if IsOwner + /// Determines if the local client has authority over the associated NetworkObject. + /// + /// In client-server contexts: returns true if `IsServer` or `IsHost`. + /// In distributed authority contexts: returns true if `IsOwner`. + /// /// public bool HasAuthority { get; internal set; } internal NetworkClient LocalClient { get; private set; } /// - /// Gets if the client is the distributed authority mode session owner + /// Gets whether the client is the distributed authority mode session owner. /// public bool IsSessionOwner { @@ -486,29 +488,29 @@ public bool IsSessionOwner } /// - /// Gets if the server (local or remote) is a host - i.e., also a client + /// Gets whether the server (local or remote) is a host. /// public bool ServerIsHost { get; private set; } /// - /// Gets if we are executing as client + /// Gets whether executing as a client. /// public bool IsClient { get; private set; } /// - /// Gets if we are executing as Host, I.E Server and Client + /// Gets whether executing as a host (both server and client). /// public bool IsHost { get; private set; } /// - /// Gets Whether or not the object has a owner + /// Gets whether the object has an owner. /// public bool IsOwnedByServer { get; internal set; } /// - /// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component - /// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate + /// Determines whether it's safe to access a NetworkObject and NetworkManager from within a NetworkBehaviour component. + /// Primarily useful when checking NetworkObject or NetworkManager properties within FixedUpate. /// public bool IsSpawned { get; internal set; } @@ -528,7 +530,7 @@ internal bool IsBehaviourEditable() /// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do /// how NetworkObject works but it was close to the release and too risky to change /// - /// Gets the NetworkObject that owns this NetworkBehaviour instance + /// Gets the NetworkObject that owns this NetworkBehaviour instance. /// public NetworkObject NetworkObject { @@ -567,19 +569,19 @@ public NetworkObject NetworkObject } /// - /// Gets whether or not this NetworkBehaviour instance has a NetworkObject owner. + /// Gets whether this NetworkBehaviour instance has a NetworkObject owner. /// public bool HasNetworkObject => NetworkObject != null; private NetworkObject m_NetworkObject = null; /// - /// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour + /// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour instance. /// public ulong NetworkObjectId { get; internal set; } /// - /// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject + /// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject. /// public ushort NetworkBehaviourId { get; internal set; } @@ -589,7 +591,7 @@ public NetworkObject NetworkObject internal ushort NetworkBehaviourIdCache = 0; /// - /// Returns a the NetworkBehaviour with a given BehaviourId for the current NetworkObject + /// Returns the NetworkBehaviour with a given BehaviourId for the current NetworkObject. /// /// The behaviourId to return /// Returns NetworkBehaviour with given behaviourId @@ -599,7 +601,7 @@ protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId) } /// - /// Gets the ClientId that owns the NetworkObject + /// Gets the ClientId that owns this NetworkObject. /// public ulong OwnerClientId { get; internal set; } @@ -651,28 +653,29 @@ internal void UpdateNetworkProperties() } /// - /// Distributed Authority Mode Only + /// Only for use in distributed authority mode. /// Invoked only on the authority instance when a is deferring its despawn on non-authoritative instances. /// /// /// See also: /// - /// the future network tick that the will be despawned on non-authoritative instances + /// The future network tick that the will be despawned on non-authoritative instances public virtual void OnDeferringDespawn(int despawnTick) { } + /// /// Gets called after the is spawned. No NetworkBehaviours associated with the NetworkObject will have had invoked yet. - /// A reference to is passed in as a parameter to determine the context of execution (IsServer/IsClient) + /// A reference to is passed in as a parameter to determine the context of execution (`IsServer` or `IsClient`). /// - /// /// a ref to the since this is not yet set on the + /// /// The will not have anything assigned to it at this point in time. - /// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn related properties will not be set. - /// This can be used to handle things like initializing/instantiating a NetworkVariable or the like. + /// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn-related properties will not be set. + /// This can be used to handle things like initializing a NetworkVariable. /// protected virtual void OnNetworkPreSpawn(ref NetworkManager networkManager) { } /// - /// Gets called when the gets spawned, message handlers are ready to be registered and the network is setup. + /// Gets called when the gets spawned, message handlers are ready to be registered, and the network is set up. /// public virtual void OnNetworkSpawn() { } @@ -686,28 +689,28 @@ public virtual void OnNetworkSpawn() { } protected virtual void OnNetworkPostSpawn() { } /// - /// [Client-Side Only] - /// When a new client joins it is synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all + /// This method is only available client-side. + /// When a new client joins it's synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all /// s and scenes (if scene management is enabled) have finished synchronizing, all NetworkBehaviour components associated with spawned s /// will have this method invoked. /// /// - /// This can be used to handle post synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. + /// This can be used to handle post-synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. /// This is only invoked on clients during a client-server network topology session. /// protected virtual void OnNetworkSessionSynchronized() { } /// - /// [Client & Server Side] - /// When a scene is loaded an in-scene placed NetworkObjects are all spawned, this method is invoked on all of the newly spawned in-scene placed NetworkObjects. + /// When a scene is loaded and in-scene placed NetworkObjects are finished spawning, this method is invoked on all of the newly spawned in-scene placed NetworkObjects. + /// This method runs both client and server side. /// /// - /// This can be used to handle post scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. + /// This method can be used to handle post-scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. /// protected virtual void OnInSceneObjectsSpawned() { } /// - /// Gets called when the gets despawned. Is called both on the server and clients. + /// Gets called when the gets despawned. This method runs both client and server side. /// public virtual void OnNetworkDespawn() { } @@ -803,7 +806,7 @@ internal void InternalOnNetworkDespawn() } /// - /// Gets called when the local client gains ownership of this object + /// Gets called when the local client gains ownership of this object. /// public virtual void OnGainedOwnership() { } @@ -814,8 +817,8 @@ internal void InternalOnGainedOwnership() } /// - /// Invoked on all clients, override this method to be notified of any - /// ownership changes (even if the instance was niether the previous or + /// Invoked on all clients. Override this method to be notified of any + /// ownership changes (even if the instance was neither the previous or /// newly assigned current owner). /// /// the previous owner @@ -831,7 +834,7 @@ internal void InternalOnOwnershipChanged(ulong previous, ulong current) } /// - /// Gets called when we loose ownership of this object + /// Gets called when ownership of this object is lost. /// public virtual void OnLostOwnership() { } @@ -842,7 +845,7 @@ internal void InternalOnLostOwnership() } /// - /// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed + /// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed. /// /// the new parent public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { } @@ -1275,7 +1278,7 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) } /// - /// Gets the local instance of a object with a given NetworkId + /// Gets the local instance of a NetworkObject with a given NetworkId. /// /// /// @@ -1286,14 +1289,14 @@ protected NetworkObject GetNetworkObject(ulong networkId) /// /// Override this method if your derived NetworkBehaviour requires custom synchronization data. - /// Note: Use of this method is only for the initial client synchronization of NetworkBehaviours + /// Use of this method is only for the initial client synchronization of NetworkBehaviours /// and will increase the payload size for client synchronization and dynamically spawned /// s. /// /// - /// When serializing (writing) this will be invoked during the client synchronization period and + /// When serializing (writing), this method is invoked during the client synchronization period and /// when spawning new NetworkObjects. - /// When deserializing (reading), this will be invoked prior to the NetworkBehaviour's associated + /// When deserializing (reading), this method is invoked prior to the NetworkBehaviour's associated /// NetworkObject being spawned. /// /// The serializer to use to read and write the data. @@ -1316,10 +1319,10 @@ public virtual void OnReanticipate(double lastRoundTripTime) /// The relative client identifier targeted for the serialization of this instance. ///
/// - /// This value will be set prior to being invoked. + /// This value is set prior to being invoked. /// For writing (server-side), this is useful to know which client will receive the serialized data. /// For reading (client-side), this will be the . - /// When synchronization of this instance is complete, this value will be reset to 0 + /// When synchronization of this instance is complete, this value is reset to 0. /// protected ulong m_TargetIdBeingSynchronized { get; private set; } @@ -1442,9 +1445,8 @@ internal bool Synchronize(ref BufferSerializer serializer, ulong targetCli /// - /// Invoked when the the is attached to. - /// NOTE: If you override this, you will want to always invoke this base class version of this - /// method!! + /// Invoked when the the is attached to is destroyed. + /// If you override this, you must always invoke the base class version of this method. /// public virtual void OnDestroy() { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index a2749ddec4..39d2262818 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -37,7 +37,7 @@ public sealed class NetworkObject : MonoBehaviour internal uint PrefabGlobalObjectIdHash; /// - /// This is the source prefab of an in-scene placed NetworkObject. This is not set for in-scene + /// This is the source prefab of an in-scene placed NetworkObject. This is not set for in-scene /// placd NetworkObjects that are not prefab instances, dynamically spawned prefab instances, /// or for network prefab assets. /// @@ -207,8 +207,8 @@ private bool IsEditingPrefab() } /// - /// This checks to see if this NetworkObject is an in-scene placed prefab instance. If so it will - /// automatically find the source prefab asset's GlobalObjectIdHash value, assign it to + /// This checks to see if this NetworkObject is an in-scene placed prefab instance. If so it will + /// automatically find the source prefab asset's GlobalObjectIdHash value, assign it to /// InScenePlacedSourceGlobalObjectIdHash and mark this as being in-scene placed. /// /// @@ -540,8 +540,8 @@ public bool SetOwnershipLock(bool lockOwnership = true) /// permission failure status codes will be returned via . /// : The is locked and ownership cannot be acquired. /// : The requires an ownership request via . - /// : The already is processing an ownership request and ownership cannot be acquired at this time. - /// does not have the flag set and ownership cannot be acquired. + /// : The is already processing an ownership request and ownership cannot be acquired at this time. + /// : The does not have the flag set and ownership cannot be acquired. ///
public enum OwnershipPermissionsFailureStatus { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 77b2e95560..975697862b 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -774,6 +774,7 @@ public void DisableValidationWarnings(bool disabled) /// /// This setting changes how clients handle scene loading when initially synchronizing with the server.
/// The server or host should set this value as clients will automatically be synchronized with the server (or host) side. + ///
/// /// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and the /// server's currently active scene will be loaded in single mode on the client unless it was already diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index c952ade4a2..ed41fc18f9 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -620,7 +620,7 @@ public static IEnumerator WaitForClientConnected(NetworkManager client, ResultWr /// Similar to WaitForClientConnected, this waits for multiple clients to be connected. ///
/// The clients to be connected - /// The result. If null, it will automatically assert< + /// The result. If null, it will automatically assert /// The max frames to wait for /// public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, ResultWrapper result = null, float timeout = DefaultTimeout) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs index 343e0bb788..ec669aeb77 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs @@ -5,11 +5,13 @@ namespace Unity.Netcode.TestHelpers.Runtime { /// /// Will automatically register for the NetworkVariable OnValueChanged - /// delegate handler. It then will expose that single delegate invocation + /// delegate handler. It then will expose that single delegate invocation /// to anything that registers for this NetworkVariableHelper's instance's OnValueChanged event. /// This allows us to register any NetworkVariable type as well as there are basically two "types of types": - /// IEquatable - /// ValueType + /// + /// IEquatable<T> + /// ValueType + /// /// From both we can then at least determine if the value indeed changed /// /// @@ -20,7 +22,7 @@ public class NetworkVariableHelper : NetworkVariableBaseHelper public event OnMyValueChangedDelegateHandler OnValueChanged; /// - /// IEquatable Equals Check + /// IEquatable<T> Equals Check /// private void CheckVariableChanged(IEquatable previous, IEquatable next) { From af564b650e038201b7c06fa03db5eaa5abb1c70f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 29 Jul 2024 07:38:41 -0500 Subject: [PATCH 070/236] chore: merge v2.0.0-pre.3 into develop-2.0.0 (#2991) * update Updating NGO to v2.0.0-pre.3 Updating UTP dependency to v2.3.0 Updating manifest to current dependencies Updating project version to 6000.0.10f1 * chore: 2.0.0-pre.3 additional last minute updates (#2978) * update missed actually updating the version. * update Added `UnityTransport.GetNetworkDriver` and `UnityTransport.GetLocalEndpoint` methods. * update Updating changelog file. * fix: metrics tooltips and serialized type non optimized warning message handling (#2979) * update Adding xml API documentation and tooltips for the `NetworkConfig.NetworkMessageMetrics` and `NetworkConfig.NetworkProfilingMetrics` properties. * update Added a static helper method in `NetworkManager` to handle logging when a serialized type is not yet optimized for distributed authority. * fix This fix includes the up-port of #2980 (RNSM not tracking RPCs in release builds). Fixing the issue where I forgot to add the serialized type for the not optimized warning message update. * fix: deferred despawn gc allocation (#2983) * fix Remove GC alloc when creating list for deferred despawn. * update adding log entry for fix. * update adding PR for the fix * chore: Anticipated not supported warning (#2982) chore notify user that this component is not supported in distributed authority mode * fix: allow clients to set client synchronization mode distributed authority (#2985) * fix Allow clients to set client synchronization mode when in distributed authority mode since clients can be promoted to session owner. * test apply same rules for the integration test version of the scene handler * update adding change log entry * chore making a new unreleased changelog entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 17 ++++++++- .../Components/AnticipatedNetworkTransform.cs | 4 +++ .../Runtime/Configuration/NetworkConfig.cs | 14 +++++++- .../Runtime/Core/NetworkBehaviour.cs | 10 +++--- .../Runtime/Core/NetworkManager.cs | 36 ++++++++++++++++++- .../Runtime/Messaging/Messages/RpcMessages.cs | 2 +- .../Messaging/RpcTargets/BaseRpcTarget.cs | 4 +-- .../RpcTargets/LocalSendRpcTarget.cs | 2 +- .../RpcTargets/ProxyRpcTargetGroup.cs | 4 +-- .../NetworkVariableSerialization.cs | 13 ++++--- .../DefaultSceneManagerHandler.cs | 7 ++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 21 +++++++---- .../Runtime/Transports/UTP/UnityTransport.cs | 25 +++++++++++++ .../Runtime/IntegrationTestSceneHandler.cs | 7 ++-- .../Runtime/NetcodeIntegrationTest.cs | 5 +++ com.unity.netcode.gameobjects/package.json | 4 +-- .../NetworkSceneManagerUsageTests.cs | 23 ++++++++++-- testproject/Packages/manifest.json | 12 +++---- .../ProjectSettings/ProjectVersion.txt | 4 +-- 19 files changed, 170 insertions(+), 44 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 99e0726ebe..d3882ecdad 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,12 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## Unreleased +## [Unreleased] ### Added ### Fixed +### Changed + + +## [2.0.0-pre.3] - 2024-07-23 + +### Added +- Added: `UnityTransport.GetNetworkDriver` and `UnityTransport.GetLocalEndpoint` methods to expose the driver and local endpoint being used. (#2978) + +### Fixed + +- Fixed issue where deferred despawn was causing GC allocations when converting an `IEnumerable` to a list. (#2983) +- Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2979) - Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded. (#2971) - Fixed issue where `Rigidbody2d` under Unity 6000.0.11f1 has breaking changes where `velocity` is now `linearVelocity` and `isKinematic` is replaced by `bodyType`. (#2971) - Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968) @@ -22,6 +34,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed logic where clients can now set the `NetworkSceneManager` client synchronization mode when using a distributed authority network topology. (#2985) + + ## [2.0.0-pre.2] - 2024-06-17 ### Added diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index e8ae5890bf..10c1d18979 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -360,6 +360,10 @@ protected override void OnSynchronize(ref BufferSerializer serializer) public override void OnNetworkSpawn() { + if (NetworkManager.DistributedAuthorityMode) + { + Debug.LogWarning($"This component is not currently supported in distributed authority."); + } base.OnNetworkSpawn(); m_OutstandingAuthorityChange = true; ApplyAuthoritativeState(); diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 9213d24fe1..79035a895f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -159,9 +159,21 @@ public class NetworkConfig public bool AutoSpawnPlayerPrefabClientSide = true; #if MULTIPLAYER_TOOLS + /// + /// Controls whether network messaging metrics will be gathered. (defaults to true) + /// There is a slight performance cost to having this enabled, and can increase in processing time based on network message traffic. + /// + /// + /// The Realtime Network Stats Monitoring tool requires this to be enabled. + /// + [Tooltip("Enable (default) if you want to gather messaging metrics. Realtime Network Stats Monitor requires this to be enabled. Disabling this can improve performance in release builds.")] public bool NetworkMessageMetrics = true; #endif - + /// + /// When enabled (default, this enables network profiling information. This does come with a per message processing cost. + /// Network profiling information is automatically disabled in release builds. + /// + [Tooltip("Enable (default) if you want to profile network messages with development builds and defaults to being disabled in release builds. When disabled, network messaging profiling will be disabled in development builds.")] public bool NetworkProfilingMetrics = true; /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 83c60ca027..c1be9fab4a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -27,7 +27,7 @@ public abstract class NetworkBehaviour : MonoBehaviour // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary> __rpc_func_table = new Dictionary>(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary> __rpc_name_table = new Dictionary>(); #endif @@ -124,7 +124,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } bufferWriter.Dispose(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) { NetworkManager.NetworkMetrics.TrackRpcSent( @@ -252,7 +252,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } bufferWriter.Dispose(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) { if (clientRpcParams.Send.TargetClientIds != null) @@ -880,7 +880,7 @@ internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMeth #pragma warning restore IDE1006 // restore naming rule violation check { __rpc_func_table[GetType()][hash] = handler; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE __rpc_name_table[GetType()][hash] = rpcMethodName; #endif } @@ -906,7 +906,7 @@ internal void InitializeVariables() if (!__rpc_func_table.ContainsKey(GetType())) { __rpc_func_table[GetType()] = new Dictionary(); -#if UNITY_EDITOR || DEVELOPMENT_BUILD +#if UNITY_EDITOR || DEVELOPMENT_BUILD || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE __rpc_name_table[GetType()] = new Dictionary(); #endif __initializeRpcs(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 95f9605729..ac38fa9a64 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -29,13 +29,40 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary __rpc_func_table = new Dictionary(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary __rpc_name_table = new Dictionary(); #endif #pragma warning restore IDE1006 // restore naming rule violation check +#if DEVELOPMENT_BUILD || UNITY_EDITOR + private static List s_SerializedType = new List(); + // This is used to control the serialized type not optimized messaging for integration test purposes + internal static bool DisableNotOptimizedSerializedType; + /// + /// Until all serialized types are optimized for the distributed authority network topology, + /// this will handle the notification to the user that the type being serialized is not yet + /// optimized but will only log the message once to prevent log spamming. + /// + internal static void LogSerializedTypeNotOptimized() + { + if (DisableNotOptimizedSerializedType) + { + return; + } + var type = typeof(T); + if (!s_SerializedType.Contains(type)) + { + s_SerializedType.Add(type); + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"[{type.Name}] Serialized type has not been optimized for use with Distributed Authority!"); + } + } + } +#endif + internal static bool IsDistributedAuthority; /// @@ -1062,6 +1089,13 @@ public int MaximumFragmentedMessageSize internal void Initialize(bool server) { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (!DisableNotOptimizedSerializedType) + { + s_SerializedType.Clear(); + } +#endif + #if COM_UNITY_MODULES_PHYSICS NetworkTransformFixedUpdate.Clear(); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 2b1406cd95..ba207e1046 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -41,7 +41,7 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) { networkManager.NetworkMetrics.TrackRpcReceived( diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs index 1ecb12ae44..3f9d4f9d88 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -36,12 +36,12 @@ protected void CheckLockBeforeDispose() private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE var size = #endif behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) { behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs index ffc9b5b07e..4c6567c262 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs @@ -46,7 +46,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, message.Handle(ref context); length = tempBuffer.Length; } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) { networkManager.NetworkMetrics.TrackRpcSent( diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs index 4109580a5a..15bb63d307 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs @@ -18,12 +18,12 @@ internal class ProxyRpcTargetGroup : BaseRpcTarget, IDisposable, IGroupRpcTarget internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message }; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE var size = #endif behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) { foreach (var clientId in TargetClientIds) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs index 23f23dbb04..bc0f79fa24 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -1,5 +1,4 @@ using System; -using UnityEngine; namespace Unity.Netcode { @@ -56,10 +55,12 @@ public static void Write(FastBufferWriter writer, ref T value) { if (IsDistributedAuthority) { - if (!Serializer.IsDistributedAuthorityOptimized) +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (!NetworkManager.DisableNotOptimizedSerializedType && !Serializer.IsDistributedAuthorityOptimized) { - Debug.Log("This variable is not optimized for use with Distributed Authority"); + NetworkManager.LogSerializedTypeNotOptimized(); } +#endif Serializer.WriteDistributedAuthority(writer, ref value); } else @@ -121,10 +122,12 @@ public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previo { if (IsDistributedAuthority) { - if (!Serializer.IsDistributedAuthorityOptimized) +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (!NetworkManager.DisableNotOptimizedSerializedType && !Serializer.IsDistributedAuthorityOptimized) { - Debug.Log("This variable is not optimized for use with Distributed Authority"); + NetworkManager.LogSerializedTypeNotOptimized(); } +#endif Serializer.WriteDeltaDistributedAuthority(writer, ref value, ref previousValue); } else diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 63203b142a..01bdff807f 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -333,8 +333,9 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode) { var sceneManager = networkManager.SceneManager; - // Don't let non-authority set this value - if ((!networkManager.DistributedAuthorityMode && !networkManager.IsServer) || (networkManager.DistributedAuthorityMode && !networkManager.LocalClient.IsSessionOwner)) + // In client-server, we don't let client's set this value. + // In distributed authority, since session owner can be promoted clients can set this value + if (!networkManager.DistributedAuthorityMode && !networkManager.IsServer) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -343,7 +344,7 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load return; } else // Warn users if they are changing this after there are clients already connected and synchronized - if (networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) + if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index dd9cd2fc8b..3f6720c7dd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1820,11 +1820,14 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) return; } var currentTick = serverTime.Tick; - var deferredCallbackObjects = DeferredDespawnObjects.Where((c) => c.HasDeferredDespawnCheck); - var deferredCallbackCount = deferredCallbackObjects.Count(); - for (int i = 0; i < deferredCallbackCount - 1; i++) + var deferredCallbackCount = DeferredDespawnObjects.Count(); + for (int i = 0; i < deferredCallbackCount; i++) { - var deferredObjectEntry = deferredCallbackObjects.ElementAt(i); + var deferredObjectEntry = DeferredDespawnObjects[i]; + if (!deferredObjectEntry.HasDeferredDespawnCheck) + { + continue; + } var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId]; // Double check to make sure user did not remove the callback if (networkObject.OnDeferredDespawnComplete != null) @@ -1849,9 +1852,15 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) } } - var despawnObjects = DeferredDespawnObjects.Where((c) => c.TickToDespawn < currentTick).ToList(); - foreach (var deferredObjectEntry in despawnObjects) + // Parse backwards so we can remove objects as we parse through them + for (int i = DeferredDespawnObjects.Count - 1; i >= 0; i--) { + var deferredObjectEntry = DeferredDespawnObjects[i]; + if (deferredObjectEntry.TickToDespawn >= currentTick) + { + continue; + } + if (!SpawnedObjects.ContainsKey(deferredObjectEntry.NetworkObjectId)) { DeferredDespawnObjects.Remove(deferredObjectEntry); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 513c3ce6ad..0237517f01 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -428,6 +428,31 @@ private struct PacketLossCache protected NetworkDriver m_Driver; + /// + /// Gets a reference to the . + /// + /// ref + public ref NetworkDriver GetNetworkDriver() + { + return ref m_Driver; + } + + /// + /// Gets the local sytem's that is assigned for the current network session. + /// + /// + /// If the driver is not created it will return an invalid . + /// + /// + public NetworkEndpoint GetLocalEndpoint() + { + if (m_Driver.IsCreated) + { + return m_Driver.GetLocalEndpoint(); + } + return new NetworkEndpoint(); + } + private PacketLossCache m_PacketLossCache = new PacketLossCache(); private State m_State = State.Disconnected; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index c444647701..a023bc1d65 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -800,8 +800,9 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load var sceneManager = networkManager.SceneManager; - // Don't let client's set this value - if (!networkManager.IsServer) + // In client-server, we don't let client's set this value. + // In dsitributed authority, since session owner can be promoted clients can set this value + if (!networkManager.DistributedAuthorityMode && !networkManager.IsServer) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -809,7 +810,7 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load } return; } - else if (networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) + else if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index a091e43284..216569355c 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -303,6 +303,11 @@ public void OneTimeSetup() OnOneTimeSetup(); VerboseDebug($"Exiting {nameof(OneTimeSetup)}"); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + // Default to not log the serialized type not optimized warning message when testing. + NetworkManager.DisableNotOptimizedSerializedType = true; +#endif } /// diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 085fac7824..db0033cce7 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,10 +2,10 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0-pre.2", + "version": "2.0.0-pre.3", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.2.1" + "com.unity.transport": "2.3.0" } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs index 9b330d79ce..e05d292c36 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerUsageTests.cs @@ -39,7 +39,14 @@ public void SceneManagementDisabled([Values(LoadSceneMode.Single, LoadSceneMode. [Test] public void ClientSetClientSynchronizationMode() { - LogAssert.Expect(UnityEngine.LogType.Warning, "[Netcode] Clients should not set this value as it is automatically synchronized with the server's setting!"); + if (!m_DistributedAuthority) + { + LogAssert.Expect(UnityEngine.LogType.Warning, "[Netcode] Clients should not set this value as it is automatically synchronized with the server's setting!"); + } + else + { + LogAssert.NoUnexpectedReceived(); + } m_ClientNetworkManagers[0].SceneManager.SetClientSynchronizationMode(LoadSceneMode.Single); } @@ -49,11 +56,21 @@ public void ClientSetClientSynchronizationMode() [UnityTest] public IEnumerator ServerSetClientSynchronizationModeAfterClientsConnected() { - // Verify that changing this setting when additional clients are connect will generate the warning - LogAssert.Expect(UnityEngine.LogType.Warning, "[Netcode] Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!"); + if (!m_DistributedAuthority) + { + // Verify that changing this setting when additional clients are connect will generate the warning + LogAssert.Expect(UnityEngine.LogType.Warning, "[Netcode] Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!"); + } + else + { + LogAssert.NoUnexpectedReceived(); + } + m_ServerNetworkManager.SceneManager.SetClientSynchronizationMode(LoadSceneMode.Additive); + // Verify that changing this setting when no additional clients are connected will not generate a warning yield return StopOneClient(m_ClientNetworkManagers[0]); + m_ServerNetworkManager.SceneManager.SetClientSynchronizationMode(LoadSceneMode.Additive); LogAssert.NoUnexpectedReceived(); } diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 7f68fd1136..40f3385f51 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,20 +1,20 @@ { "dependencies": { - "com.unity.addressables": "2.0.8", + "com.unity.addressables": "2.1.0", "com.unity.ai.navigation": "2.0.0", - "com.unity.collab-proxy": "2.3.1", + "com.unity.collab-proxy": "2.4.3", "com.unity.ide.rider": "3.0.28", "com.unity.ide.visualstudio": "2.0.22", "com.unity.mathematics": "1.3.2", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.49.0-preview", - "com.unity.services.authentication": "3.3.1", - "com.unity.services.core": "1.12.5", + "com.unity.services.authentication": "3.3.3", + "com.unity.services.core": "1.13.0", "com.unity.services.relay": "1.0.5", "com.unity.test-framework": "1.4.4", "com.unity.test-framework.performance": "3.0.3", - "com.unity.timeline": "1.8.6", - "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", + "com.unity.timeline": "1.8.7", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.9", "com.unity.ugui": "2.0.0", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index f2ebefc901..59d74ae16d 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.1f1 -m_EditorVersionWithRevision: 6000.0.1f1 (d9cf669c6271) +m_EditorVersion: 6000.0.10f1 +m_EditorVersionWithRevision: 6000.0.10f1 (413673acabac) From 9ef01bab066e778b6b8463cf88092ba00d3da128 Mon Sep 17 00:00:00 2001 From: Amy Reeve Date: Mon, 5 Aug 2024 11:20:30 +0100 Subject: [PATCH 071/236] fix: Improve API doc for OnGainedOwnership and OnLostOwnership (#2997) --- .../Runtime/Core/NetworkBehaviour.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index c1be9fab4a..5e088c7f06 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -806,7 +806,8 @@ internal void InternalOnNetworkDespawn() } /// - /// Gets called when the local client gains ownership of this object. + /// In client-server contexts, this method is invoked on both the server and the local client of the owner when ownership is assigned. + /// In distributed authority contexts, this method is only invoked on the local client that has been assigned ownership of the associated . /// public virtual void OnGainedOwnership() { } @@ -834,7 +835,9 @@ internal void InternalOnOwnershipChanged(ulong previous, ulong current) } /// - /// Gets called when ownership of this object is lost. + /// In client-server contexts, this method is invoked on the local client when it loses ownership of the associated + /// and on the server when any client loses ownership. + /// In distributed authority contexts, this method is only invoked on the local client that has lost ownership of the associated . /// public virtual void OnLostOwnership() { } @@ -1138,7 +1141,7 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie // Distributed Authority: All clients have read permissions, always try to write the value. if (NetworkVariableFields[j].CanClientRead(targetClientId)) { - // Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode + // Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode if (ensureLengthSafety || distributedAuthority) { // Write the type being serialized for distributed authority (only for comb-server) From 04825ac509cc2d4fcdc0f410856b334897bc8565 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 5 Aug 2024 16:40:31 -0500 Subject: [PATCH 072/236] feat: add ping tool to public repo (#3001) * update Adding project for PingTool example. * update adding packages * update adding manifest * update removing packages-lock * update Adding a README.md file * update adjusting offsets of images --- Examples/PingTool/.gitignore | 83 ++ .../Assets/DefaultNetworkPrefabs.asset | 31 + .../Assets/DefaultNetworkPrefabs.asset.meta | 8 + Examples/PingTool/Assets/Materials.meta | 8 + .../PingTool/Assets/Materials/ChildLocal.mat | 86 ++ .../Assets/Materials/ChildLocal.mat.meta | 8 + .../Materials/ChildLocalNoParentSync.mat | 86 ++ .../Materials/ChildLocalNoParentSync.mat.meta | 8 + .../PingTool/Assets/Materials/ChildWorld.mat | 86 ++ .../Assets/Materials/ChildWorld.mat.meta | 8 + .../Materials/ChildWorldNoParentSync.mat | 86 ++ .../Materials/ChildWorldNoParentSync.mat.meta | 8 + .../PingTool/Assets/Materials/Direction.mat | 84 ++ .../Assets/Materials/Direction.mat.meta | 8 + .../PingTool/Assets/Materials/GridPattern.mat | 85 ++ .../Assets/Materials/GridPattern.mat.meta | 8 + Examples/PingTool/Assets/Materials/Ground.mat | 79 ++ .../PingTool/Assets/Materials/Ground.mat.meta | 8 + .../Assets/Materials/PlayerMaterial.mat | 80 ++ .../Assets/Materials/PlayerMaterial.mat.meta | 8 + Examples/PingTool/Assets/PingTool.meta | 8 + .../NetStateMonitorConfiguration.asset | 188 ++++ .../NetStateMonitorConfiguration.asset.meta | 8 + .../Assets/PingTool/NetStatsMonitor.prefab | 55 ++ .../PingTool/NetStatsMonitor.prefab.meta | 7 + .../NetStatsMonitorPanelSettings.asset | 46 + .../NetStatsMonitorPanelSettings.asset.meta | 8 + Examples/PingTool/Assets/PingTool/PingTool.cs | 679 +++++++++++++ .../PingTool/Assets/PingTool/PingTool.cs.meta | 2 + .../PingTool/Assets/PingTool/PingTool.prefab | 802 +++++++++++++++ .../Assets/PingTool/PingTool.prefab.meta | 7 + .../UnityMpToolsRnsmDefaultStyleSheet.uss | 163 ++++ ...UnityMpToolsRnsmDefaultStyleSheet.uss.meta | 11 + ....unity.netcode.gameobjects.pingtool.asmdef | 26 + ...y.netcode.gameobjects.pingtool.asmdef.meta | 7 + Examples/PingTool/Assets/Prefabs.meta | 8 + .../PingTool/Assets/Prefabs/Player.prefab | 231 +++++ .../Assets/Prefabs/Player.prefab.meta | 7 + .../Assets/Prefabs/SceneLevelGeometry.prefab | 779 +++++++++++++++ .../Prefabs/SceneLevelGeometry.prefab.meta | 7 + Examples/PingTool/Assets/Resources.meta | 8 + .../Assets/Resources/BillingMode.json | 1 + .../Assets/Resources/BillingMode.json.meta | 7 + Examples/PingTool/Assets/Scenes.meta | 8 + Examples/PingTool/Assets/Scenes/Camera.preset | 195 ++++ .../PingTool/Assets/Scenes/Camera.preset.meta | 8 + .../PingTool/Assets/Scenes/SampleScene.unity | 923 ++++++++++++++++++ .../Assets/Scenes/SampleScene.unity.meta | 7 + .../Assets/Scenes/SampleScenePrefabs.asset | 16 + .../Scenes/SampleScenePrefabs.asset.meta | 8 + Examples/PingTool/Assets/Scripts.meta | 8 + .../Assets/Scripts/NetworkManagerHelper.cs | 350 +++++++ .../Scripts/NetworkManagerHelper.cs.meta | 11 + .../Assets/Scripts/OnDisconnectNotify.cs | 35 + .../Assets/Scripts/OnDisconnectNotify.cs.meta | 11 + .../PingTool/Assets/Scripts/PlayerColor.cs | 37 + .../Assets/Scripts/PlayerColor.cs.meta | 11 + .../PingTool/Assets/Scripts/PlayerMotion.cs | 100 ++ .../Assets/Scripts/PlayerMotion.cs.meta | 11 + .../Assets/Scripts/ServerHostClientText.cs | 79 ++ .../Scripts/ServerHostClientText.cs.meta | 11 + Examples/PingTool/Assets/Textures.meta | 8 + .../PingTool/Assets/Textures/GridPattern.png | Bin 0 -> 3152344 bytes .../Assets/Textures/GridPattern.png.meta | 153 +++ Examples/PingTool/Packages/manifest.json | 55 ++ .../ProjectSettings/EditorBuildSettings.asset | 11 + .../ProjectSettings/ProjectSettings.asset | 690 +++++++++++++ .../PingTool/ProjectSettings/TagManager.asset | 45 + .../ProjectSettings/TimeManager.asset | 13 + .../UnityConnectSettings.asset | 36 + .../VersionControlSettings.asset | 7 + Examples/PingTool/README.md | 55 ++ 72 files changed, 6833 insertions(+) create mode 100644 Examples/PingTool/.gitignore create mode 100644 Examples/PingTool/Assets/DefaultNetworkPrefabs.asset create mode 100644 Examples/PingTool/Assets/DefaultNetworkPrefabs.asset.meta create mode 100644 Examples/PingTool/Assets/Materials.meta create mode 100644 Examples/PingTool/Assets/Materials/ChildLocal.mat create mode 100644 Examples/PingTool/Assets/Materials/ChildLocal.mat.meta create mode 100644 Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat create mode 100644 Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat.meta create mode 100644 Examples/PingTool/Assets/Materials/ChildWorld.mat create mode 100644 Examples/PingTool/Assets/Materials/ChildWorld.mat.meta create mode 100644 Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat create mode 100644 Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat.meta create mode 100644 Examples/PingTool/Assets/Materials/Direction.mat create mode 100644 Examples/PingTool/Assets/Materials/Direction.mat.meta create mode 100644 Examples/PingTool/Assets/Materials/GridPattern.mat create mode 100644 Examples/PingTool/Assets/Materials/GridPattern.mat.meta create mode 100644 Examples/PingTool/Assets/Materials/Ground.mat create mode 100644 Examples/PingTool/Assets/Materials/Ground.mat.meta create mode 100644 Examples/PingTool/Assets/Materials/PlayerMaterial.mat create mode 100644 Examples/PingTool/Assets/Materials/PlayerMaterial.mat.meta create mode 100644 Examples/PingTool/Assets/PingTool.meta create mode 100644 Examples/PingTool/Assets/PingTool/NetStateMonitorConfiguration.asset create mode 100644 Examples/PingTool/Assets/PingTool/NetStateMonitorConfiguration.asset.meta create mode 100644 Examples/PingTool/Assets/PingTool/NetStatsMonitor.prefab create mode 100644 Examples/PingTool/Assets/PingTool/NetStatsMonitor.prefab.meta create mode 100644 Examples/PingTool/Assets/PingTool/NetStatsMonitorPanelSettings.asset create mode 100644 Examples/PingTool/Assets/PingTool/NetStatsMonitorPanelSettings.asset.meta create mode 100644 Examples/PingTool/Assets/PingTool/PingTool.cs create mode 100644 Examples/PingTool/Assets/PingTool/PingTool.cs.meta create mode 100644 Examples/PingTool/Assets/PingTool/PingTool.prefab create mode 100644 Examples/PingTool/Assets/PingTool/PingTool.prefab.meta create mode 100644 Examples/PingTool/Assets/PingTool/UnityMpToolsRnsmDefaultStyleSheet.uss create mode 100644 Examples/PingTool/Assets/PingTool/UnityMpToolsRnsmDefaultStyleSheet.uss.meta create mode 100644 Examples/PingTool/Assets/PingTool/com.unity.netcode.gameobjects.pingtool.asmdef create mode 100644 Examples/PingTool/Assets/PingTool/com.unity.netcode.gameobjects.pingtool.asmdef.meta create mode 100644 Examples/PingTool/Assets/Prefabs.meta create mode 100644 Examples/PingTool/Assets/Prefabs/Player.prefab create mode 100644 Examples/PingTool/Assets/Prefabs/Player.prefab.meta create mode 100644 Examples/PingTool/Assets/Prefabs/SceneLevelGeometry.prefab create mode 100644 Examples/PingTool/Assets/Prefabs/SceneLevelGeometry.prefab.meta create mode 100644 Examples/PingTool/Assets/Resources.meta create mode 100644 Examples/PingTool/Assets/Resources/BillingMode.json create mode 100644 Examples/PingTool/Assets/Resources/BillingMode.json.meta create mode 100644 Examples/PingTool/Assets/Scenes.meta create mode 100644 Examples/PingTool/Assets/Scenes/Camera.preset create mode 100644 Examples/PingTool/Assets/Scenes/Camera.preset.meta create mode 100644 Examples/PingTool/Assets/Scenes/SampleScene.unity create mode 100644 Examples/PingTool/Assets/Scenes/SampleScene.unity.meta create mode 100644 Examples/PingTool/Assets/Scenes/SampleScenePrefabs.asset create mode 100644 Examples/PingTool/Assets/Scenes/SampleScenePrefabs.asset.meta create mode 100644 Examples/PingTool/Assets/Scripts.meta create mode 100644 Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs create mode 100644 Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs.meta create mode 100644 Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs create mode 100644 Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs.meta create mode 100644 Examples/PingTool/Assets/Scripts/PlayerColor.cs create mode 100644 Examples/PingTool/Assets/Scripts/PlayerColor.cs.meta create mode 100644 Examples/PingTool/Assets/Scripts/PlayerMotion.cs create mode 100644 Examples/PingTool/Assets/Scripts/PlayerMotion.cs.meta create mode 100644 Examples/PingTool/Assets/Scripts/ServerHostClientText.cs create mode 100644 Examples/PingTool/Assets/Scripts/ServerHostClientText.cs.meta create mode 100644 Examples/PingTool/Assets/Textures.meta create mode 100644 Examples/PingTool/Assets/Textures/GridPattern.png create mode 100644 Examples/PingTool/Assets/Textures/GridPattern.png.meta create mode 100644 Examples/PingTool/Packages/manifest.json create mode 100644 Examples/PingTool/ProjectSettings/EditorBuildSettings.asset create mode 100644 Examples/PingTool/ProjectSettings/ProjectSettings.asset create mode 100644 Examples/PingTool/ProjectSettings/TagManager.asset create mode 100644 Examples/PingTool/ProjectSettings/TimeManager.asset create mode 100644 Examples/PingTool/ProjectSettings/UnityConnectSettings.asset create mode 100644 Examples/PingTool/ProjectSettings/VersionControlSettings.asset create mode 100644 Examples/PingTool/README.md diff --git a/Examples/PingTool/.gitignore b/Examples/PingTool/.gitignore new file mode 100644 index 0000000000..2800399634 --- /dev/null +++ b/Examples/PingTool/.gitignore @@ -0,0 +1,83 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore +# +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Asset meta data should only be ignored when the corresponding asset is also ignored +!/[Aa]ssets/**/*.meta + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.aab +*.unitypackage + +# Crashlytics generated file +crashlytics-build.properties + +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json.meta + +# Secrets +*.pem +*.pem.meta + +InitTestScene* + +boot.config +SceneTemplateSettings.json +*BurstAotSettings*.json diff --git a/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset b/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset new file mode 100644 index 0000000000..073f4484e8 --- /dev/null +++ b/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e651dbb3fbac04af2b8f5abf007ddc23, type: 3} + m_Name: DefaultNetworkPrefabs + m_EditorClassIdentifier: + IsDefault: 1 + List: + - Override: 0 + Prefab: {fileID: 2522762726852386808, guid: 380c984d34fc8664c8f53fc1d8733a25, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 8921789205124766477, guid: 89b57e576a8d47643b2dbd45b1f8cab1, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 3439633038736912633, guid: 398aad09d8b2a47eba664a076763cdcc, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} diff --git a/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset.meta b/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset.meta new file mode 100644 index 0000000000..fee27b3ade --- /dev/null +++ b/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aa82390bfdde2564f828b8e5be375282 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials.meta b/Examples/PingTool/Assets/Materials.meta new file mode 100644 index 0000000000..463de70d61 --- /dev/null +++ b/Examples/PingTool/Assets/Materials.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d5b7ad71451c27e4291295cfffc10328 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/ChildLocal.mat b/Examples/PingTool/Assets/Materials/ChildLocal.mat new file mode 100644 index 0000000000..b6fd2dd9a1 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildLocal.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildLocal + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.8980392, g: 0.039215658, b: 0.7682729, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/ChildLocal.mat.meta b/Examples/PingTool/Assets/Materials/ChildLocal.mat.meta new file mode 100644 index 0000000000..35e4d565be --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildLocal.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45fc555bc05bfee4ab8b0d536799ecee +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat b/Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat new file mode 100644 index 0000000000..c44172e7a7 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildLocalNoParentSync + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.039215658, g: 0.78592235, b: 0.8980392, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat.meta b/Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat.meta new file mode 100644 index 0000000000..3a26e43d98 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildLocalNoParentSync.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: affef70511a06dd46b8f52636020af4a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/ChildWorld.mat b/Examples/PingTool/Assets/Materials/ChildWorld.mat new file mode 100644 index 0000000000..0c2bf4b187 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildWorld.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildWorld + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.3783494, g: 0.039215658, b: 0.8980392, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/ChildWorld.mat.meta b/Examples/PingTool/Assets/Materials/ChildWorld.mat.meta new file mode 100644 index 0000000000..9a00ded8c6 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildWorld.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15d0bda12a233964086aee5c0c357e24 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat b/Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat new file mode 100644 index 0000000000..86b27eb5c9 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildWorldNoParentSync + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.039215658, g: 0.8980392, b: 0.09095798, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat.meta b/Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat.meta new file mode 100644 index 0000000000..5ab7ff2e72 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/ChildWorldNoParentSync.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a5e199307b2e0894294d9c8bee99a691 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/Direction.mat b/Examples/PingTool/Assets/Materials/Direction.mat new file mode 100644 index 0000000000..ed5ed117c3 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/Direction.mat @@ -0,0 +1,84 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Direction + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 0.8962264, g: 0.52830994, b: 0.038047332, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/Direction.mat.meta b/Examples/PingTool/Assets/Materials/Direction.mat.meta new file mode 100644 index 0000000000..c93791fac0 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/Direction.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5324c76c2bab7344badd5ea27a40bcb5 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/GridPattern.mat b/Examples/PingTool/Assets/Materials/GridPattern.mat new file mode 100644 index 0000000000..f44981f387 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/GridPattern.mat @@ -0,0 +1,85 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridPattern + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _GLOSSYREFLECTIONS_OFF + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 1 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 6, y: 6} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: a092c5fa8c60ed04aa1d72555f1740bc, type: 3} + m_Scale: {x: 6, y: 6} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0.785 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0.254717, g: 0.23188858, b: 0.23188858, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/GridPattern.mat.meta b/Examples/PingTool/Assets/Materials/GridPattern.mat.meta new file mode 100644 index 0000000000..cbca0f4485 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/GridPattern.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 42c4a0ad1f9d67a45b12f68697321aad +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/Ground.mat b/Examples/PingTool/Assets/Materials/Ground.mat new file mode 100644 index 0000000000..252ea1a0ed --- /dev/null +++ b/Examples/PingTool/Assets/Materials/Ground.mat @@ -0,0 +1,79 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Ground + m_Shader: {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + m_ShaderKeywords: + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 0.14769986, g: 0.1509434, b: 0.1473834, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/Ground.mat.meta b/Examples/PingTool/Assets/Materials/Ground.mat.meta new file mode 100644 index 0000000000..6dbcac16d3 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/Ground.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9c73b921ea39f4344a19c2d1c7d6b314 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/Materials/PlayerMaterial.mat b/Examples/PingTool/Assets/Materials/PlayerMaterial.mat new file mode 100644 index 0000000000..e7f5729956 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/PlayerMaterial.mat @@ -0,0 +1,80 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: PlayerMaterial + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/PingTool/Assets/Materials/PlayerMaterial.mat.meta b/Examples/PingTool/Assets/Materials/PlayerMaterial.mat.meta new file mode 100644 index 0000000000..1ceca58536 --- /dev/null +++ b/Examples/PingTool/Assets/Materials/PlayerMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 44e292334941fe148b997ca2b01b5789 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/PingTool.meta b/Examples/PingTool/Assets/PingTool.meta new file mode 100644 index 0000000000..ba9359124c --- /dev/null +++ b/Examples/PingTool/Assets/PingTool.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce8d435e959ea9a41b4c776fc31f9550 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/PingTool/Assets/PingTool/NetStateMonitorConfiguration.asset b/Examples/PingTool/Assets/PingTool/NetStateMonitorConfiguration.asset new file mode 100644 index 0000000000..8748ea24db --- /dev/null +++ b/Examples/PingTool/Assets/PingTool/NetStateMonitorConfiguration.asset @@ -0,0 +1,188 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f51159758cdc14734b2cb6ec1e40e18e, type: 3} + m_Name: NetStateMonitorConfiguration + m_EditorClassIdentifier: + k__BackingField: + - k__BackingField: 1 + k__BackingField: 2 + - public AsyncOperation AsyncOperation; + public AsyncOperationHandle AsyncOperation; /// /// Will always be set to the current @@ -181,7 +185,7 @@ public class NetworkSceneManager : IDisposable /// name of the scene being processed /// the LoadSceneMode mode for the scene being loaded /// the associated that can be used for scene loading progress - public delegate void OnLoadDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation); + public delegate void OnLoadDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode, AsyncOperationHandle asyncOperation); /// /// Delegate declaration for the OnUnload event.
@@ -191,7 +195,7 @@ public class NetworkSceneManager : IDisposable /// the client that is processing this event (the server will receive all of these events for every client and itself) /// name of the scene being processed /// the associated that can be used for scene unloading progress - public delegate void OnUnloadDelegateHandler(ulong clientId, string sceneName, AsyncOperation asyncOperation); + public delegate void OnUnloadDelegateHandler(ulong clientId, string sceneName, AsyncOperationHandle asyncOperation); /// /// Delegate declaration for the OnSynchronize event.
@@ -394,7 +398,7 @@ public bool ActiveSceneSynchronizationEnabled /// /// The SceneManagerHandler implementation /// - internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler(); + public ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler(); internal readonly Dictionary SceneEventProgressTracking = new Dictionary(); @@ -413,6 +417,17 @@ public bool ActiveSceneSynchronizationEnabled ///
internal Scene SceneBeingSynchronized; + public class SceneData + { + public SceneData(SceneInstance? instance, Scene reference) + { + SceneReference = reference; + SceneInstance = instance; + } + public Scene SceneReference; + public SceneInstance? SceneInstance; + } + /// /// Used to track which scenes are currently loaded /// We store the scenes as [SceneHandle][Scene] in order to handle the loading and unloading of the same scene additively @@ -421,7 +436,7 @@ public bool ActiveSceneSynchronizationEnabled /// The client links the server scene handle to the client local scene handle upon a scene being loaded /// /// - internal Dictionary ScenesLoaded = new Dictionary(); + public Dictionary ScenesLoaded = new Dictionary(); /// /// Since Scene.handle is unique per client, we create a look-up table between the client and server to associate server unique scene @@ -458,7 +473,7 @@ internal bool UpdateServerClientSceneHandle(int serverHandle, int clientHandle, // It is "Ok" if this already has an entry if (!ScenesLoaded.ContainsKey(clientHandle)) { - ScenesLoaded.Add(clientHandle, localScene); + ScenesLoaded.Add(clientHandle, new SceneData(null, localScene)); } return true; @@ -500,16 +515,6 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) return true; } - /// - /// Hash to build index lookup table - /// - internal Dictionary HashToBuildIndex = new Dictionary(); - - /// - /// Build index to hash lookup table - /// - internal Dictionary BuildIndexToHash = new Dictionary(); - /// /// The Condition: While a scene is asynchronously loaded in single loading scene mode, if any new NetworkObjects are spawned /// they need to be moved into the do not destroy temporary scene @@ -525,10 +530,12 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) /// internal Dictionary SceneEventDataStore; - internal readonly NetworkManager NetworkManager; + internal Dictionary ScenePathsBySceneName; + + private NetworkManager NetworkManager { get; } // Keep track of this scene until the NetworkSceneManager is destroyed. - internal Scene DontDestroyOnLoadScene; + public Scene DontDestroyOnLoadScene; /// /// This setting changes how clients handle scene loading when initially synchronizing with the server.
@@ -543,7 +550,7 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) /// and, if is /// set, callback(s). /// - public LoadSceneMode ClientSynchronizationMode { get; internal set; } + public LoadSceneMode ClientSynchronizationMode { get; set; } /// /// When true, the messages will be turned off @@ -639,104 +646,6 @@ internal bool ShouldDeferCreateObject() return (synchronizeEventDetected && ClientSynchronizationMode == LoadSceneMode.Single) || (!synchronizeEventDetected && loadingEventDetected); } - /// - /// Gets the scene name from full path to the scene - /// - internal string GetSceneNameFromPath(string scenePath) - { - var begin = scenePath.LastIndexOf("/", StringComparison.Ordinal) + 1; - var end = scenePath.LastIndexOf(".", StringComparison.Ordinal); - return scenePath.Substring(begin, end - begin); - } - - /// - /// Generates the hash values and associated tables - /// for the scenes in build list - /// - internal void GenerateScenesInBuild() - { - // TODO 2023: We could support addressable or asset bundle scenes by - // adding a method that would allow users to add scenes to this. - // The method would be server-side only and require an additional SceneEventType - // that would be used to notify clients of the added scene. This might need - // to include information about the addressable or asset bundle (i.e. address to load assets) - HashToBuildIndex.Clear(); - BuildIndexToHash.Clear(); - for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++) - { - var scenePath = SceneUtility.GetScenePathByBuildIndex(i); - var hash = XXHash.Hash32(scenePath); - var buildIndex = SceneUtility.GetBuildIndexByScenePath(scenePath); - - // In the rare-case scenario where a programmatically generated build has duplicate - // scene entries, we will log an error and skip the entry - if (!HashToBuildIndex.ContainsKey(hash)) - { - HashToBuildIndex.Add(hash, buildIndex); - BuildIndexToHash.Add(buildIndex, hash); - } - else - { - Debug.LogError($"{nameof(NetworkSceneManager)} is skipping duplicate scene path entry {scenePath}. Make sure your scenes in build list does not contain duplicates!"); - } - } - } - - /// - /// Gets the scene name from a hash value generated from the full scene path - /// - internal string SceneNameFromHash(uint sceneHash) - { - // In the event there is no scene associated with the scene event then just return "No Scene" - // This can happen during unit tests when clients first connect and the only scene loaded is the - // unit test scene (which is ignored by default) that results in a scene event that has no associated - // scene. Under this specific special case, we just return "No Scene". - if (sceneHash == 0) - { - return "No Scene"; - } - return GetSceneNameFromPath(ScenePathFromHash(sceneHash)); - } - - /// - /// Gets the full scene path from a hash value - /// - internal string ScenePathFromHash(uint sceneHash) - { - if (HashToBuildIndex.ContainsKey(sceneHash)) - { - return SceneUtility.GetScenePathByBuildIndex(HashToBuildIndex[sceneHash]); - } - else - { - throw new Exception($"Scene Hash {sceneHash} does not exist in the {nameof(HashToBuildIndex)} table! Verify that all scenes requiring" + - $" server to client synchronization are in the scenes in build list."); - } - } - - /// - /// Gets the associated hash value for the scene name or path - /// - internal uint SceneHashFromNameOrPath(string sceneNameOrPath) - { - var buildIndex = SceneUtility.GetBuildIndexByScenePath(sceneNameOrPath); - if (buildIndex >= 0) - { - if (BuildIndexToHash.ContainsKey(buildIndex)) - { - return BuildIndexToHash[buildIndex]; - } - else - { - throw new Exception($"Scene '{sceneNameOrPath}' has a build index of {buildIndex} that does not exist in the {nameof(BuildIndexToHash)} table!"); - } - } - else - { - throw new Exception($"Scene '{sceneNameOrPath}' couldn't be loaded because it has not been added to the build settings scenes in build list."); - } - } - /// /// When set to true, this will disable the console warnings about /// a scene being invalidated. @@ -776,13 +685,14 @@ internal NetworkSceneManager(NetworkManager networkManager) NetworkManager = networkManager; SceneEventDataStore = new Dictionary(); - // Generates the scene name to hash value - GenerateScenesInBuild(); - // Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene DontDestroyOnLoadScene = networkManager.gameObject.scene; - // Since the server tracks loaded scenes, we need to add any currently loaded scenes on the + ServerSceneHandleToClientSceneHandle.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle); + + ScenesLoaded.Add(DontDestroyOnLoadScene.handle, new SceneData(null, DontDestroyOnLoadScene)); + + // Since the server tracks loaded scenes, we need to add any currently loaded scenes on the // server side when the NetworkManager is started and NetworkSceneManager instantiated when // scene management is enabled. if (networkManager.IsServer && networkManager.NetworkConfig.EnableSceneManagement) @@ -790,7 +700,7 @@ internal NetworkSceneManager(NetworkManager networkManager) for (int i = 0; i < SceneManager.sceneCount; i++) { var loadedScene = SceneManager.GetSceneAt(i); - ScenesLoaded.Add(loadedScene.handle, loadedScene); + ScenesLoaded.Add(loadedScene.handle, new SceneData(null, loadedScene)); } SceneManagerHandler.PopulateLoadedScenes(ref ScenesLoaded, NetworkManager); } @@ -819,16 +729,12 @@ private void SceneManager_ActiveSceneChanged(Scene current, Scene next) } } - // If the scene's build index is in the hash table - if (BuildIndexToHash.ContainsKey(next.buildIndex)) - { - // Notify clients of the change in active scene - var sceneEvent = BeginSceneEvent(); - sceneEvent.SceneEventType = SceneEventType.ActiveSceneChanged; - sceneEvent.ActiveSceneHash = BuildIndexToHash[next.buildIndex]; - SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray()); - EndSceneEvent(sceneEvent.SceneEventId); - } + // Notify clients of the change in active scene + var sceneEvent = BeginSceneEvent(); + sceneEvent.SceneEventType = SceneEventType.ActiveSceneChanged; + sceneEvent.ActiveSceneAsset = next.name; + SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray()); + EndSceneEvent(sceneEvent.SceneEventId); } /// @@ -839,20 +745,10 @@ private void SceneManager_ActiveSceneChanged(Scene current, Scene next) /// index into ScenesInBuild /// LoadSceneMode the scene is going to be loaded /// true (Valid) or false (Invalid) - internal bool ValidateSceneBeforeLoading(uint sceneHash, LoadSceneMode loadSceneMode) - { - var sceneName = SceneNameFromHash(sceneHash); - var sceneIndex = SceneUtility.GetBuildIndexByScenePath(sceneName); - return ValidateSceneBeforeLoading(sceneIndex, sceneName, loadSceneMode); - } - - /// - /// Overloaded version that is invoked by and . - /// This specifically is to allow runtime generated scenes to be excluded by the server during synchronization. - /// - internal bool ValidateSceneBeforeLoading(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) + internal bool ValidateSceneBeforeLoading(string sceneName, LoadSceneMode loadSceneMode) { var validated = true; + var sceneIndex = SceneUtility.GetBuildIndexByScenePath(sceneName); if (VerifySceneBeforeLoading != null) { validated = VerifySceneBeforeLoading.Invoke(sceneIndex, sceneName, loadSceneMode); @@ -900,7 +796,7 @@ internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName) { if (!ScenesLoaded.ContainsKey(sceneLoaded.handle)) { - ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded); + ScenesLoaded.Add(sceneLoaded.handle, new SceneData(null, sceneLoaded)); SceneManagerHandler.StartTrackingScene(sceneLoaded, true, NetworkManager); return sceneLoaded; } @@ -934,7 +830,7 @@ internal void SetTheSceneBeingSynchronized(int serverSceneHandle) } // Get the scene currently being synchronized - SceneBeingSynchronized = ScenesLoaded.ContainsKey(clientSceneHandle) ? ScenesLoaded[clientSceneHandle] : new Scene(); + SceneBeingSynchronized = ScenesLoaded.ContainsKey(clientSceneHandle) ? ScenesLoaded[clientSceneHandle].SceneReference : new Scene(); if (!SceneBeingSynchronized.IsValid() || !SceneBeingSynchronized.isLoaded) { @@ -1006,7 +902,7 @@ private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds) }; var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, targetClientIds); - NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size); + NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneEventDataStore[sceneEventId].SceneAsset, size); } /// @@ -1073,16 +969,53 @@ private SceneEventProgress ValidateSceneEvent(string sceneName, bool isUnloading return new SceneEventProgress(null, SceneEventProgressStatus.SceneEventInProgress); } - // Return invalid scene name status if the scene name is invalid - if (SceneUtility.GetBuildIndexByScenePath(sceneName) == InvalidSceneNameOrPath) + // // Return invalid scene name status if the scene name is invalid + // if (SceneUtility.GetBuildIndexByScenePath(sceneName) == InvalidSceneNameOrPath) + // { + // Debug.LogError($"Scene '{sceneName}' couldn't be loaded because it has not been added to the build settings scenes in build list."); + // return new SceneEventProgress(null, SceneEventProgressStatus.InvalidSceneName); + // } + var locatorInfo = Addressables.GetLocatorInfo(sceneName); + + var resourceLocationAsync = Addressables.LoadResourceLocationsAsync(sceneName); + resourceLocationAsync.WaitForCompletion(); + if (resourceLocationAsync.Status == AsyncOperationStatus.Succeeded) + { + if (resourceLocationAsync.Result.Count >= 1) + { + var location = resourceLocationAsync.Result[0]; + var provider = location.ProviderId; + if (!provider.Contains("Scene")) + { + Debug.LogWarning($"Provider is not a scene provider! {provider}"); + } + + var resourceType = location.ResourceType; + if (resourceType != typeof(SceneInstance)) + { + throw new Exception($"Scene is not of the SceneInstance type! {resourceType}"); + } + if (location.HasDependencies) + { + // Has dependencies! + // download something? + // no! this is just a verification method + } + + if (location.PrimaryKey != sceneName) + { + throw new Exception($"Scene is not primary key of the scene name! {location.PrimaryKey} {sceneName}"); + } + } + } + else { - Debug.LogError($"Scene '{sceneName}' couldn't be loaded because it has not been added to the build settings scenes in build list."); - return new SceneEventProgress(null, SceneEventProgressStatus.InvalidSceneName); + throw new Exception($"Scene '{sceneName}' couldn't be loaded for its resource location."); } var sceneEventProgress = new SceneEventProgress(NetworkManager) { - SceneHash = SceneHashFromNameOrPath(sceneName) + SceneName = sceneName }; SceneEventProgressTracking.Add(sceneEventProgress.Guid, sceneEventProgress); @@ -1105,7 +1038,7 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress var clientsThatCompleted = sceneEventProgress.GetClientsWithStatus(true); var clientsThatTimedOut = sceneEventProgress.GetClientsWithStatus(false); sceneEventData.SceneEventProgressId = sceneEventProgress.Guid; - sceneEventData.SceneHash = sceneEventProgress.SceneHash; + sceneEventData.SceneAsset = sceneEventProgress.SceneName; sceneEventData.SceneEventType = sceneEventProgress.SceneEventType; sceneEventData.ClientsCompleted = clientsThatCompleted; sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode; @@ -1120,14 +1053,14 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress NetworkManager.NetworkMetrics.TrackSceneEventSent( NetworkManager.ConnectedClientsIds, (uint)sceneEventProgress.SceneEventType, - SceneNameFromHash(sceneEventProgress.SceneHash), + sceneEventProgress.SceneName, size); // Send a local notification to the server that all clients are done loading or unloading OnSceneEvent?.Invoke(new SceneEvent() { SceneEventType = sceneEventProgress.SceneEventType, - SceneName = SceneNameFromHash(sceneEventProgress.SceneHash), + SceneName = sceneEventProgress.SceneName, ClientId = NetworkManager.ServerClientId, LoadSceneMode = sceneEventProgress.LoadSceneMode, ClientsThatCompleted = clientsThatCompleted, @@ -1136,11 +1069,11 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted) { - OnLoadEventCompleted?.Invoke(SceneNameFromHash(sceneEventProgress.SceneHash), sceneEventProgress.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); + OnLoadEventCompleted?.Invoke(sceneEventProgress.SceneName, sceneEventProgress.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); } else { - OnUnloadEventCompleted?.Invoke(SceneNameFromHash(sceneEventProgress.SceneHash), sceneEventProgress.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); + OnUnloadEventCompleted?.Invoke(sceneEventProgress.SceneName, sceneEventProgress.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); } EndSceneEvent(sceneEventData.SceneEventId); @@ -1186,7 +1119,7 @@ public SceneEventProgressStatus UnloadScene(Scene scene) var sceneEventData = BeginSceneEvent(); sceneEventData.SceneEventProgressId = sceneEventProgress.Guid; sceneEventData.SceneEventType = SceneEventType.Unload; - sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName); + sceneEventData.SceneAsset = sceneName; sceneEventData.LoadSceneMode = LoadSceneMode.Additive; // The only scenes unloaded are scenes that were additively loaded sceneEventData.SceneHandle = sceneHandle; @@ -1196,7 +1129,18 @@ public SceneEventProgressStatus UnloadScene(Scene scene) ScenesLoaded.Remove(scene.handle); sceneEventProgress.SceneEventId = sceneEventData.SceneEventId; sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded; - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); + + AsyncOperationHandle sceneUnload; + if (ScenesLoaded[sceneHandle].SceneInstance.HasValue) + { + sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle].SceneInstance.Value, sceneEventProgress); + } + else + { + throw new Exception("Fuuuuck"); + sceneUnload = default; + } + // Notify local server that a scene is going to be unloaded OnSceneEvent?.Invoke(new SceneEvent() { @@ -1220,7 +1164,7 @@ public SceneEventProgressStatus UnloadScene(Scene scene) private void OnClientUnloadScene(uint sceneEventId) { var sceneEventData = SceneEventDataStore[sceneEventId]; - var sceneName = SceneNameFromHash(sceneEventData.SceneHash); + var sceneName = sceneEventData.SceneAsset; if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.SceneHandle)) { @@ -1244,7 +1188,7 @@ private void OnClientUnloadScene(uint sceneEventId) // 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); + SceneManagerHandler.MoveObjectsFromSceneToDontDestroyOnLoad(ref networkManager, scene.SceneReference); m_IsSceneEventActive = true; var sceneEventProgress = new SceneEventProgress(NetworkManager) @@ -1252,7 +1196,17 @@ private void OnClientUnloadScene(uint sceneEventId) SceneEventId = sceneEventData.SceneEventId, OnSceneEventCompleted = OnSceneUnloaded }; - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress); + + AsyncOperationHandle opHandle; + if (ScenesLoaded[sceneHandle].SceneInstance.HasValue) + { + opHandle = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle].SceneInstance.Value, sceneEventProgress); + } + else + { + throw new Exception("Fuuuuck"); + opHandle = default; + } SceneManagerHandler.StopTrackingScene(sceneHandle, sceneName, NetworkManager); @@ -1266,21 +1220,21 @@ private void OnClientUnloadScene(uint sceneEventId) // Notify the local client that a scene is going to be unloaded OnSceneEvent?.Invoke(new SceneEvent() { - AsyncOperation = sceneUnload, + AsyncOperation = opHandle, SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded SceneName = sceneName, ClientId = NetworkManager.LocalClientId // Server sent this message to the client, but client is executing it }); - OnUnload?.Invoke(NetworkManager.LocalClientId, sceneName, sceneUnload); + OnUnload?.Invoke(NetworkManager.LocalClientId, sceneName, opHandle); } /// /// Server and Client: /// Invoked when an additively loaded scene is unloaded /// - private void OnSceneUnloaded(uint sceneEventId) + private void OnSceneUnloaded(uint sceneEventId, string sceneName) { // If we are shutdown or about to shutdown, then ignore this event if (!NetworkManager.IsListening || NetworkManager.ShutdownInProgress) @@ -1315,11 +1269,11 @@ private void OnSceneUnloaded(uint sceneEventId) { SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = SceneNameFromHash(sceneEventData.SceneHash), + SceneName = sceneEventData.SceneAsset, ClientId = NetworkManager.IsServer ? NetworkManager.ServerClientId : NetworkManager.LocalClientId }); - OnUnloadComplete?.Invoke(NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash)); + OnUnloadComplete?.Invoke(NetworkManager.LocalClientId, sceneEventData.SceneAsset); // Clients send a notification back to the server they have completed the unload scene event if (!NetworkManager.IsServer) @@ -1332,7 +1286,7 @@ private void OnSceneUnloaded(uint sceneEventId) m_IsSceneEventActive = false; } - private void EmptySceneUnloadedOperation(uint sceneEventId) + private void EmptySceneUnloadedOperation(uint sceneEventId, string sceneName) { // Do nothing (this is a stub call since it is only used to flush all additively loaded scenes) } @@ -1347,25 +1301,116 @@ internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) var sceneEventData = SceneEventDataStore[sceneEventId]; // Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ). var currentActiveScene = SceneManager.GetActiveScene(); + + Queue removedScenes = new(); + foreach (var keyHandleEntry in ScenesLoaded) { // Validate the scene as well as ignore the DDOL (which will have a negative buildIndex) - if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0) + if (currentActiveScene.name != keyHandleEntry.Value.SceneReference.name) { - var sceneEventProgress = new SceneEventProgress(NetworkManager) + var sceneEventProgress = new SceneEventProgress(NetworkManager); + sceneEventProgress.SceneEventId = sceneEventId; + sceneEventProgress.OnSceneEventCompleted = EmptySceneUnloadedOperation; + + if (keyHandleEntry.Value.SceneInstance != null) { - SceneEventId = sceneEventId, - OnSceneEventCompleted = EmptySceneUnloadedOperation - }; - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress); - SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload); + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value.SceneInstance.Value, sceneEventProgress); + + SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value.SceneReference, LoadSceneMode.Additive, sceneUnload); + removedScenes.Enqueue(keyHandleEntry.Key); + } } } // clear out our scenes loaded list - ScenesLoaded.Clear(); + while (removedScenes.Count > 0) + { + var cur = removedScenes.Dequeue(); + ScenesLoaded.Remove(cur); + } SceneManagerHandler.ClearSceneTracking(NetworkManager); } + public SceneEventProgress LoadAddressableScene(AssetReference sceneReference, LoadSceneMode loadSceneMode) + { + var resourceAsync = Addressables.LoadResourceLocationsAsync(sceneReference); + resourceAsync.WaitForCompletion(); + + var sceneName = ""; + + if (resourceAsync.Status == AsyncOperationStatus.Succeeded) + { + var sceneKey = resourceAsync.Result[0].PrimaryKey; + sceneName = sceneKey; + } + else + { + throw new Exception($"Failed to load scene from resource {resourceAsync.OperationException}"); + } + + var sceneEventProgress = ValidateSceneEventLoading(sceneName); + if (sceneEventProgress.Status != SceneEventProgressStatus.Started) + { + return sceneEventProgress; + } + + // This will be the message we send to everyone when this scene event sceneEventProgress is complete + sceneEventProgress.SceneEventType = SceneEventType.LoadEventCompleted; + sceneEventProgress.LoadSceneMode = loadSceneMode; + + var sceneEventData = BeginSceneEvent(); + + // Now set up the current scene event + sceneEventData.SceneEventProgressId = sceneEventProgress.Guid; + sceneEventData.SceneEventType = SceneEventType.Load; + sceneEventData.SceneAsset = sceneName; + sceneEventData.LoadSceneMode = loadSceneMode; + var sceneEventId = sceneEventData.SceneEventId; + // This both checks to make sure the scene is valid and if not resets the active scene event + m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneAsset, loadSceneMode); + if (!m_IsSceneEventActive) + { + EndSceneEvent(sceneEventId); + sceneEventProgress.Status = SceneEventProgressStatus.SceneFailedVerification; + return sceneEventProgress; + } + + if (sceneEventData.LoadSceneMode == LoadSceneMode.Single) + { + // Destroy current scene objects before switching. + NetworkManager.SpawnManager.ServerDestroySpawnedSceneObjects(); + + // Preserve the objects that should not be destroyed during the scene event + MoveObjectsToDontDestroyOnLoad(); + + // Now Unload all currently additively loaded scenes + UnloadAdditivelyLoadedScenes(sceneEventId); + + // Register the active scene for unload scene event notifications + SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single); + } + + // Now start loading the scene + sceneEventProgress.SceneEventId = sceneEventId; + sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded; + var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); + + // Notify the local server that a scene loading event has begun + OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = sceneLoad, + SceneEventType = sceneEventData.SceneEventType, + LoadSceneMode = sceneEventData.LoadSceneMode, + SceneName = sceneName, + ClientId = NetworkManager.ServerClientId + }); + + OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); + + //Return our scene progress instance + return sceneEventProgress; + } + /// /// Server side: /// Loads the scene name in either additive or single loading mode. @@ -1374,12 +1419,12 @@ internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) /// the name of the scene to be loaded /// how the scene will be loaded (single or additive mode) /// ( means it was successful) - public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSceneMode) + public SceneEventProgress LoadScene(string sceneName, LoadSceneMode loadSceneMode) { var sceneEventProgress = ValidateSceneEventLoading(sceneName); if (sceneEventProgress.Status != SceneEventProgressStatus.Started) { - return sceneEventProgress.Status; + return sceneEventProgress; } // This will be the message we send to everyone when this scene event sceneEventProgress is complete @@ -1391,15 +1436,15 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc // Now set up the current scene event sceneEventData.SceneEventProgressId = sceneEventProgress.Guid; sceneEventData.SceneEventType = SceneEventType.Load; - sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName); + sceneEventData.SceneAsset = sceneName; sceneEventData.LoadSceneMode = loadSceneMode; var sceneEventId = sceneEventData.SceneEventId; // This both checks to make sure the scene is valid and if not resets the active scene event - m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode); + m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneAsset, loadSceneMode); if (!m_IsSceneEventActive) { EndSceneEvent(sceneEventId); - return SceneEventProgressStatus.SceneFailedVerification; + return new SceneEventProgress(NetworkManager.Singleton, SceneEventProgressStatus.SceneFailedVerification); } if (sceneEventData.LoadSceneMode == LoadSceneMode.Single) @@ -1441,7 +1486,7 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad); //Return our scene progress instance - return sceneEventProgress.Status; + return sceneEventProgress; } /// @@ -1452,7 +1497,7 @@ internal class SceneUnloadEventHandler { private static Dictionary> s_Instances = new Dictionary>(); - internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null) + internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperationHandle asyncOperation = default) { var networkManager = networkSceneManager.NetworkManager; if (!s_Instances.ContainsKey(networkManager)) @@ -1497,7 +1542,7 @@ internal static void Shutdown() } private NetworkSceneManager m_NetworkSceneManager; - private AsyncOperation m_AsyncOperation; + private AsyncOperationHandle m_AsyncOperation; private LoadSceneMode m_LoadSceneMode; private ulong m_ClientId; private Scene m_Scene; @@ -1530,7 +1575,7 @@ private void SceneUnloaded(Scene scene) } } - private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene scene, ulong clientId, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null) + private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene scene, ulong clientId, LoadSceneMode loadSceneMode, AsyncOperationHandle asyncOperation = default) { m_LoadSceneMode = loadSceneMode; m_AsyncOperation = asyncOperation; @@ -1548,7 +1593,7 @@ private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene s ClientId = clientId }); - m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.NetworkManager.LocalClientId, m_Scene.name, null); + m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.NetworkManager.LocalClientId, m_Scene.name, default); } } @@ -1560,10 +1605,10 @@ private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene s private void OnClientSceneLoadingEvent(uint sceneEventId) { var sceneEventData = SceneEventDataStore[sceneEventId]; - var sceneName = SceneNameFromHash(sceneEventData.SceneHash); + var sceneName = sceneEventData.SceneAsset; // Run scene validation before loading a scene - if (!ValidateSceneBeforeLoading(sceneEventData.SceneHash, sceneEventData.LoadSceneMode)) + if (!ValidateSceneBeforeLoading(sceneEventData.SceneAsset, sceneEventData.LoadSceneMode)) { EndSceneEvent(sceneEventId); return; @@ -1614,7 +1659,7 @@ private void OnClientSceneLoadingEvent(uint sceneEventId) /// Client and Server: /// Generic on scene loaded callback method to be called upon a scene loading /// - private void OnSceneLoaded(uint sceneEventId) + private void OnSceneLoaded(uint sceneEventId, string loadedSceneName) { // If we are shutdown or about to shutdown, then ignore this event if (!NetworkManager.IsListening || NetworkManager.ShutdownInProgress) @@ -1623,11 +1668,12 @@ private void OnSceneLoaded(uint sceneEventId) } var sceneEventData = SceneEventDataStore[sceneEventId]; - var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash)); - if (!nextScene.isLoaded || !nextScene.IsValid()) + var nextScene = GetAndAddNewlyLoadedSceneByName(loadedSceneName); + if (!nextScene.IsValid()) { throw new Exception($"Failed to find valid scene internal Unity.Netcode for {nameof(GameObject)}s error!"); } + // If we async loaded a single scene, the active will activate it if (sceneEventData.LoadSceneMode == LoadSceneMode.Single) { @@ -1673,6 +1719,8 @@ private void OnSceneLoaded(uint sceneEventId) /// private void OnServerLoadedScene(uint sceneEventId, Scene scene) { + Debug.Log($"NetworkSceneManager - OnServerLoadedScene eventId:{sceneEventId} scene:{scene.name}"); + var sceneEventData = SceneEventDataStore[sceneEventId]; // Register in-scene placed NetworkObjects with spawn manager foreach (var keyValuePairByGlobalObjectIdHash in ScenePlacedObjects) @@ -1727,12 +1775,12 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene) { SceneEventType = SceneEventType.LoadComplete, LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = SceneNameFromHash(sceneEventData.SceneHash), + SceneName = sceneEventData.SceneAsset, ClientId = NetworkManager.ServerClientId, Scene = scene, }); - OnLoadComplete?.Invoke(NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); + OnLoadComplete?.Invoke(NetworkManager.ServerClientId, sceneEventData.SceneAsset, sceneEventData.LoadSceneMode); //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && NetworkManager.IsHost) @@ -1763,12 +1811,12 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) { SceneEventType = SceneEventType.LoadComplete, LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = SceneNameFromHash(sceneEventData.SceneHash), + SceneName = sceneEventData.SceneAsset, ClientId = NetworkManager.LocalClientId, Scene = scene, }); - OnLoadComplete?.Invoke(NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); + OnLoadComplete?.Invoke(NetworkManager.LocalClientId, sceneEventData.SceneAsset, sceneEventData.LoadSceneMode); EndSceneEvent(sceneEventId); } @@ -1800,10 +1848,10 @@ internal void SynchronizeNetworkObjects(ulong clientId) sceneEventData.LoadSceneMode = ClientSynchronizationMode; var activeScene = SceneManager.GetActiveScene(); sceneEventData.SceneEventType = SceneEventType.Synchronize; - if (BuildIndexToHash.ContainsKey(activeScene.buildIndex)) - { - sceneEventData.ActiveSceneHash = BuildIndexToHash[activeScene.buildIndex]; - } + // if (BuildIndexToHash.ContainsKey(activeScene.buildIndex)) + // { + // sceneEventData.ActiveSceneHash = BuildIndexToHash[activeScene.buildIndex]; + // } // Organize how (and when) we serialize our NetworkObjects for (int i = 0; i < SceneManager.sceneCount; i++) @@ -1817,6 +1865,7 @@ internal void SynchronizeNetworkObjects(ulong clientId) continue; } + var sceneHash = scene.name; if (scene == DontDestroyOnLoadScene) { continue; @@ -1826,18 +1875,18 @@ internal void SynchronizeNetworkObjects(ulong clientId) // If we are the base scene, then we set the root scene index; if (activeScene == scene) { - if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, sceneEventData.LoadSceneMode)) + if (!ValidateSceneBeforeLoading(sceneHash, sceneEventData.LoadSceneMode)) { continue; } - sceneEventData.SceneHash = SceneHashFromNameOrPath(scene.path); + sceneEventData.SceneAsset = scene.name; sceneEventData.SceneHandle = scene.handle; } - else if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) + else if (!ValidateSceneBeforeLoading(sceneHash, LoadSceneMode.Additive)) { continue; } - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + sceneEventData.AddSceneToSynchronize(sceneHash, scene.handle); } sceneEventData.AddSpawnedNetworkObjects(); @@ -1872,17 +1921,17 @@ private void OnClientBeginSync(uint sceneEventId) var sceneEventData = SceneEventDataStore[sceneEventId]; var sceneHash = sceneEventData.GetNextSceneSynchronizationHash(); var sceneHandle = sceneEventData.GetNextSceneSynchronizationHandle(); - var sceneName = SceneNameFromHash(sceneHash); + var sceneName = sceneHash; var activeScene = SceneManager.GetActiveScene(); - var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; + var loadSceneMode = sceneHash == sceneEventData.SceneAsset ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; // Store the sceneHandle and hash sceneEventData.NetworkSceneHandle = sceneHandle; - sceneEventData.ClientSceneHash = sceneHash; + sceneEventData.SceneAsset = sceneHash; // If this is the beginning of the synchronization event, then send client a notification that synchronization has begun - if (sceneHash == sceneEventData.SceneHash) + if (sceneHash == sceneEventData.SceneAsset) { OnSceneEvent?.Invoke(new SceneEvent() { @@ -1904,14 +1953,14 @@ private void OnClientBeginSync(uint sceneEventId) return; } - var sceneLoad = (AsyncOperation)null; + var sceneLoad = (AsyncOperationHandle)default; // Determines if the client has the scene to be loaded already loaded, if so will return true and the client will skip loading this scene // For ClientSynchronizationMode LoadSceneMode.Single, we pass in whether the scene being loaded is the first/primary active scene and if it is already loaded // it should pass through to post load processing (ClientLoadedSynchronization). // For ClientSynchronizationMode LoadSceneMode.Additive, if the scene is already loaded or the active scene is the scene to be loaded (does not require it to // be the initial primary scene) then go ahead and pass through to post load processing (ClientLoadedSynchronization). - var shouldPassThrough = SceneManagerHandler.ClientShouldPassThrough(sceneName, sceneHash == sceneEventData.SceneHash, ClientSynchronizationMode, NetworkManager); + var shouldPassThrough = SceneManagerHandler.ClientShouldPassThrough(sceneName, sceneName == sceneEventData.SceneAsset, ClientSynchronizationMode, NetworkManager); if (!shouldPassThrough) { @@ -1938,7 +1987,7 @@ private void OnClientBeginSync(uint sceneEventId) else { // If so, then pass through - ClientLoadedSynchronization(sceneEventId); + ClientLoadedSynchronization(sceneEventId, sceneName); } } @@ -1947,10 +1996,9 @@ private void OnClientBeginSync(uint sceneEventId) /// This handles all of the in-scene and dynamically spawned NetworkObject synchronization /// /// Netcode scene index that was loaded - private void ClientLoadedSynchronization(uint sceneEventId) + private void ClientLoadedSynchronization(uint sceneEventId, string sceneName) { var sceneEventData = SceneEventDataStore[sceneEventId]; - var sceneName = SceneNameFromHash(sceneEventData.ClientSceneHash); var nextScene = SceneManagerHandler.GetSceneFromLoadedScenes(sceneName, NetworkManager); if (!nextScene.IsValid()) { @@ -1962,7 +2010,7 @@ private void ClientLoadedSynchronization(uint sceneEventId) throw new Exception($"Failed to find valid scene internal Unity.Netcode for {nameof(GameObject)}s error!"); } - var loadSceneMode = (sceneEventData.ClientSceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive); + var loadSceneMode = (sceneEventData.ClientSceneName == sceneEventData.SceneAsset ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive); // For now, during a synchronization event, we will make the first scene the "base/master" scene that denotes a "complete scene switch" if (loadSceneMode == LoadSceneMode.Single) @@ -1984,7 +2032,7 @@ private void ClientLoadedSynchronization(uint sceneEventId) var responseSceneEventData = BeginSceneEvent(); responseSceneEventData.LoadSceneMode = loadSceneMode; responseSceneEventData.SceneEventType = SceneEventType.LoadComplete; - responseSceneEventData.SceneHash = sceneEventData.ClientSceneHash; + responseSceneEventData.SceneAsset = sceneEventData.ClientSceneName; var message = new SceneEventMessage @@ -2038,12 +2086,12 @@ private void SynchronizeNetworkObjectScene() if (ScenesLoaded.ContainsKey(networkObject.SceneOriginHandle)) { var scene = ScenesLoaded[networkObject.SceneOriginHandle]; - if (scene == DontDestroyOnLoadScene) + if (scene.SceneReference == DontDestroyOnLoadScene) { Debug.Log($"{networkObject.gameObject.name} migrating into DDOL!"); } - SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); + SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene.SceneReference); } else if (NetworkManager.LogLevel <= LogLevel.Normal) { @@ -2067,13 +2115,10 @@ private void HandleClientSceneEvent(uint sceneEventId) { case SceneEventType.ActiveSceneChanged: { - if (HashToBuildIndex.ContainsKey(sceneEventData.ActiveSceneHash)) + var scene = SceneManager.GetSceneByName(sceneEventData.ClientSceneName); + if (scene.isLoaded) { - var scene = SceneManager.GetSceneByBuildIndex(HashToBuildIndex[sceneEventData.ActiveSceneHash]); - if (scene.isLoaded) - { - SceneManager.SetActiveScene(scene); - } + SceneManager.SetActiveScene(scene); } break; } @@ -2104,13 +2149,10 @@ private void HandleClientSceneEvent(uint sceneEventId) PopulateScenePlacedObjects(DontDestroyOnLoadScene, false); // If needed, set the currently active scene - if (HashToBuildIndex.ContainsKey(sceneEventData.ActiveSceneHash)) + var targetActiveScene = SceneManager.GetSceneByName(sceneEventData.ClientSceneName); + if (targetActiveScene.isLoaded && targetActiveScene.handle != SceneManager.GetActiveScene().handle) { - var targetActiveScene = SceneManager.GetSceneByBuildIndex(HashToBuildIndex[sceneEventData.ActiveSceneHash]); - if (targetActiveScene.isLoaded && targetActiveScene.handle != SceneManager.GetActiveScene().handle) - { - SceneManager.SetActiveScene(targetActiveScene); - } + SceneManager.SetActiveScene(targetActiveScene); } // Spawn and Synchronize all NetworkObjects @@ -2183,7 +2225,7 @@ private void HandleClientSceneEvent(uint sceneEventId) { SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = SceneNameFromHash(sceneEventData.SceneHash), + SceneName = sceneEventData.SceneAsset, ClientId = NetworkManager.ServerClientId, ClientsThatCompleted = sceneEventData.ClientsCompleted, ClientsThatTimedOut = sceneEventData.ClientsTimedOut, @@ -2191,11 +2233,11 @@ private void HandleClientSceneEvent(uint sceneEventId) if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted) { - OnLoadEventCompleted?.Invoke(SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); + OnLoadEventCompleted?.Invoke(sceneEventData.SceneAsset, sceneEventData.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); } else { - OnUnloadEventCompleted?.Invoke(SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); + OnUnloadEventCompleted?.Invoke(sceneEventData.SceneAsset, sceneEventData.LoadSceneMode, sceneEventData.ClientsCompleted, sceneEventData.ClientsTimedOut); } EndSceneEvent(sceneEventId); @@ -2226,11 +2268,11 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) { SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = SceneNameFromHash(sceneEventData.SceneHash), + SceneName = sceneEventData.SceneAsset, ClientId = clientId }); - OnLoadComplete?.Invoke(clientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode); + OnLoadComplete?.Invoke(clientId, sceneEventData.SceneAsset, sceneEventData.LoadSceneMode); if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId)) { @@ -2250,11 +2292,11 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) { SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = SceneNameFromHash(sceneEventData.SceneHash), + SceneName = sceneEventData.SceneAsset, ClientId = clientId }); - OnUnloadComplete?.Invoke(clientId, SceneNameFromHash(sceneEventData.SceneHash)); + OnUnloadComplete?.Invoke(clientId, sceneEventData.SceneAsset); EndSceneEvent(sceneEventId); break; @@ -2327,7 +2369,7 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) sceneEventData.Deserialize(reader); NetworkManager.NetworkMetrics.TrackSceneEventReceived( - clientId, (uint)sceneEventData.SceneEventType, SceneNameFromHash(sceneEventData.SceneHash), reader.Length); + clientId, (uint)sceneEventData.SceneEventType, sceneEventData.SceneAsset, reader.Length); if (sceneEventData.IsSceneEventClientSide()) { @@ -2534,7 +2576,7 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) /// or invoked by when a client finishes /// synchronization. /// - internal void MigrateNetworkObjectsIntoScenes() + public void MigrateNetworkObjectsIntoScenes() { try { @@ -2548,7 +2590,7 @@ internal void MigrateNetworkObjectsIntoScenes() var scene = ScenesLoaded[clientSceneHandle]; foreach (var networkObject in sceneEntry.Value) { - SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); + SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene.SceneReference); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 712740ad59..d2f45338a6 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Unity.Collections; +using UnityEngine; using UnityEngine.SceneManagement; namespace Unity.Netcode @@ -104,12 +105,12 @@ internal class SceneEventData : IDisposable internal ForceNetworkSerializeByMemcpy SceneEventProgressId; internal uint SceneEventId; - internal uint ActiveSceneHash; - internal uint SceneHash; + internal string ActiveSceneAsset; + internal string SceneAsset; internal int SceneHandle; // Used by the client during synchronization - internal uint ClientSceneHash; + internal string ClientSceneName; internal int NetworkSceneHandle; /// Only used for scene events, this assures permissions when writing @@ -146,7 +147,7 @@ internal class SceneEventData : IDisposable internal List ClientsCompleted; internal List ClientsTimedOut; - internal Queue ScenesToSynchronize; + internal Queue ScenesToSynchronize; internal Queue SceneHandlesToSynchronize; internal LoadSceneMode ClientSynchronizationMode; @@ -166,7 +167,7 @@ internal class SceneEventData : IDisposable /// /// /// - internal void AddSceneToSynchronize(uint sceneHash, int sceneHandle) + internal void AddSceneToSynchronize(string sceneHash, int sceneHandle) { ScenesToSynchronize.Enqueue(sceneHash); SceneHandlesToSynchronize.Enqueue((uint)sceneHandle); @@ -177,7 +178,7 @@ internal void AddSceneToSynchronize(uint sceneHash, int sceneHandle) /// Gets the next scene hash to be loaded for approval and/or late joining /// /// - internal uint GetNextSceneSynchronizationHash() + internal string GetNextSceneSynchronizationHash() { return ScenesToSynchronize.Dequeue(); } @@ -228,7 +229,7 @@ internal void InitializeForSynch() if (ScenesToSynchronize == null) { - ScenesToSynchronize = new Queue(); + ScenesToSynchronize = new Queue(); } else { @@ -441,7 +442,7 @@ internal void Serialize(FastBufferWriter writer) if (SceneEventType == SceneEventType.ActiveSceneChanged) { - writer.WriteValueSafe(ActiveSceneHash); + writer.WriteValueSafe(ActiveSceneAsset); return; } @@ -465,14 +466,21 @@ internal void Serialize(FastBufferWriter writer) } // Write the scene index and handle - writer.WriteValueSafe(SceneHash); + var sceneName = SceneAsset ?? ""; + writer.WriteValueSafe(sceneName); writer.WriteValueSafe(SceneHandle); switch (SceneEventType) { case SceneEventType.Synchronize: { - writer.WriteValueSafe(ActiveSceneHash); + if (ActiveSceneAsset == null) + { + Debug.LogError($"Synchronizing - ActiveSceneAsset but it hasn't been initialized yet"); + ActiveSceneAsset = ""; + } + + writer.WriteValueSafe(ActiveSceneAsset); WriteSceneSynchronizationData(writer); break; } @@ -508,7 +516,13 @@ internal void Serialize(FastBufferWriter writer) internal void WriteSceneSynchronizationData(FastBufferWriter writer) { // Write the scenes we want to load, in the order we want to load them - writer.WriteValueSafe(ScenesToSynchronize.ToArray()); + var valArray = ScenesToSynchronize.ToArray(); + writer.WriteValueSafe(valArray.Length); + foreach (var v in valArray) + { + writer.WriteValueSafe(v); + } + writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray()); // Store our current position in the stream to come back and say how much data we have written @@ -610,7 +624,7 @@ internal void Deserialize(FastBufferReader reader) reader.ReadValueSafe(out SceneEventType); if (SceneEventType == SceneEventType.ActiveSceneChanged) { - reader.ReadValueSafe(out ActiveSceneHash); + reader.ReadValueSafe(out ActiveSceneAsset); return; } @@ -640,14 +654,14 @@ internal void Deserialize(FastBufferReader reader) reader.ReadValueSafe(out ClientSynchronizationMode); } - reader.ReadValueSafe(out SceneHash); + reader.ReadValueSafe(out SceneAsset); reader.ReadValueSafe(out SceneHandle); switch (SceneEventType) { case SceneEventType.Synchronize: { - reader.ReadValueSafe(out ActiveSceneHash); + reader.ReadValueSafe(out ActiveSceneAsset); CopySceneSynchronizationData(reader); break; } @@ -691,9 +705,15 @@ internal void Deserialize(FastBufferReader reader) internal void CopySceneSynchronizationData(FastBufferReader reader) { m_NetworkObjectsSync.Clear(); - reader.ReadValueSafe(out uint[] scenesToSynchronize); + reader.ReadValueSafe(out int sceneCount); + ScenesToSynchronize = new Queue(); + for (int i = 0; i < sceneCount; i++) + { + reader.ReadValueSafe(out string s); + ScenesToSynchronize.Enqueue(s); + } + reader.ReadValueSafe(out uint[] sceneHandlesToSynchronize); - ScenesToSynchronize = new Queue(scenesToSynchronize); SceneHandlesToSynchronize = new Queue(sceneHandlesToSynchronize); // is not packed! @@ -812,6 +832,7 @@ internal void ReadClientReSynchronizationData(FastBufferReader reader) } } } + Debug.LogError($"ReadClientReSynchronizationData END"); } /// @@ -1008,6 +1029,10 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) DeserializeDespawnedInScenePlacedNetworkObjects(); } + catch (Exception e) + { + Debug.LogError($"Hit an exception while serializing scene object! " + e); + } finally { InternalBuffer.Dispose(); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index d50c6c73ea..f87c2f538b 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using UnityEngine; +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; using AsyncOperation = UnityEngine.AsyncOperation; @@ -61,7 +63,7 @@ public enum SceneEventProgressStatus /// Server side only: /// This tracks the progress of clients during a load or unload scene event /// - internal class SceneEventProgress + public class SceneEventProgress { /// /// List of clientIds of those clients that is done loading the scene. @@ -77,14 +79,14 @@ internal class SceneEventProgress /// /// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out. /// - internal delegate bool OnCompletedDelegate(SceneEventProgress sceneEventProgress); + public delegate bool OnCompletedDelegate(SceneEventProgress sceneEventProgress); /// /// The callback invoked when the switch scene progress is completed. Either by all clients done loading the scene or by time out. /// - internal OnCompletedDelegate OnComplete; + public OnCompletedDelegate OnComplete; - internal Action OnSceneEventCompleted; + public Action OnSceneEventCompleted; /// /// This will make sure that we only have timed out if we never completed @@ -97,17 +99,17 @@ internal bool HasTimedOut() /// /// The hash value generated from the full scene path /// - internal uint SceneHash { get; set; } + internal string SceneName { get; set; } internal Guid Guid { get; } = Guid.NewGuid(); internal uint SceneEventId; private Coroutine m_TimeOutCoroutine; - private AsyncOperation m_AsyncOperation; + private AsyncOperationHandle m_AsyncOperation; private NetworkManager m_NetworkManager { get; } - internal SceneEventProgressStatus Status { get; set; } + public SceneEventProgressStatus Status { get; set; } internal SceneEventType SceneEventType { get; set; } @@ -120,7 +122,7 @@ internal List GetClientsWithStatus(bool completedSceneEvent) { // If we are the host, then add the host-client to the list // of clients that completed if the AsyncOperation is done. - if (m_NetworkManager.IsHost && m_AsyncOperation.isDone) + if (m_NetworkManager.IsHost && m_AsyncOperation.IsDone) { clients.Add(m_NetworkManager.LocalClientId); } @@ -139,7 +141,7 @@ internal List GetClientsWithStatus(bool completedSceneEvent) // If we are the host, then add the host-client to the list // of clients that did not complete if the AsyncOperation is // not done. - if (m_NetworkManager.IsHost && !m_AsyncOperation.isDone) + if (m_NetworkManager.IsHost && !m_AsyncOperation.IsDone) { clients.Add(m_NetworkManager.LocalClientId); } @@ -252,22 +254,59 @@ private bool HasFinished() // Note: Integration tests process scene loading through a queue // and the AsyncOperation could not be assigned for several // network tick periods. Return false if that is the case. - return m_AsyncOperation == null ? false : m_AsyncOperation.isDone; + + // If we're async loading a scene that we tell not to activate, we need to check that the downstream scene has been activated before calling out + var initialValid = m_AsyncOperation.IsValid() && m_AsyncOperation.IsDone; + if (initialValid) + { + var res = m_AsyncOperation.Result; + return res.Scene.isLoaded; + } + + return false; + } + + public AsyncOperationHandle GetHandle() + { + return m_AsyncOperation; } /// /// Sets the AsyncOperation for the scene load/unload event /// - internal void SetAsyncOperation(AsyncOperation asyncOperation) + public void SetAsyncOperation(AsyncOperationHandle asyncOperation) { + // Debug.Log($"[SceneEventProgress] SetAsyncOperation "); m_AsyncOperation = asyncOperation; - m_AsyncOperation.completed += new Action(asyncOp2 => + m_AsyncOperation.Completed += new Action>(asyncOp2 => { // Don't invoke the callback if the network session is disconnected // during a SceneEventProgress - if (IsNetworkSessionActive()) + if (asyncOp2.Status == AsyncOperationStatus.Succeeded) + { + var sceneInstance = asyncOp2.Result; + if (!sceneInstance.Scene.isLoaded) + { + var asyncLoad = sceneInstance.ActivateAsync(); + asyncLoad.completed += operation => + { + if (IsNetworkSessionActive()) + { + OnSceneEventCompleted?.Invoke(SceneEventId, asyncOp2.Result.Scene.name); + } + }; + } + else + { + if (IsNetworkSessionActive()) + { + OnSceneEventCompleted?.Invoke(SceneEventId, asyncOp2.Result.Scene.name); + } + } + } + else { - OnSceneEventCompleted?.Invoke(SceneEventId); + Debug.LogError($"Failed to load async scene: {asyncOp2.OperationException}"); } // Go ahead and try finishing even if the network session is terminated/terminating diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 7c7386bb6f..769cf61f51 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -704,6 +704,7 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkObject.IsPlayerObject = playerObject; + SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject); SpawnedObjectsList.Add(networkObject); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 984da63ee8..450362489a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -425,7 +425,7 @@ private struct PacketLossCache internal static event Action TransportInitialized; internal static event Action TransportDisposed; - internal NetworkDriver NetworkDriver => m_Driver; + public NetworkDriver NetworkDriver => m_Driver; private PacketLossCache m_PacketLossCache = new PacketLossCache(); diff --git a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef index 3b673da866..c7c45eb5dd 100644 --- a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef +++ b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef @@ -13,7 +13,9 @@ "Unity.Networking.Transport", "Unity.Collections", "Unity.Burst", - "Unity.Mathematics" + "Unity.Mathematics", + "Unity.ResourceManager", + "Unity.Addressables" ], "allowUnsafeCode": true, "versionDefines": [ diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index a9784a9b5b..010a1390bc 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; using Object = UnityEngine.Object; @@ -57,7 +60,7 @@ public enum JobTypes } public JobTypes JobType; public string SceneName; - public Scene Scene; + public SceneInstance Scene; public SceneEventProgress SceneEventProgress; public IntegrationTestSceneHandler IntegrationTestSceneHandler; } @@ -116,7 +119,8 @@ internal static IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob SceneManager.sceneLoaded += SceneManager_sceneLoaded; // We always load additively for all scenes during integration tests - var asyncOperation = SceneManager.LoadSceneAsync(queuedSceneJob.SceneName, LoadSceneMode.Additive); + var asyncOperation = Addressables.LoadSceneAsync(queuedSceneJob.SceneName, LoadSceneMode.Additive); + queuedSceneJob.SceneEventProgress.SetAsyncOperation(asyncOperation); // Wait for it to finish @@ -206,9 +210,9 @@ internal static IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJ } SceneManager.sceneUnloaded += SceneManager_sceneUnloaded; - if (queuedSceneJob.Scene.IsValid() && queuedSceneJob.Scene.isLoaded && !queuedSceneJob.Scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName)) + if (queuedSceneJob.Scene.Scene.IsValid() && queuedSceneJob.Scene.Scene.isLoaded && !queuedSceneJob.Scene.Scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName)) { - var asyncOperation = SceneManager.UnloadSceneAsync(queuedSceneJob.Scene); + var asyncOperation = Addressables.UnloadSceneAsync(queuedSceneJob.Scene); queuedSceneJob.SceneEventProgress.SetAsyncOperation(asyncOperation); } else @@ -228,7 +232,7 @@ internal static IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJ /// private static void SceneManager_sceneUnloaded(Scene scene) { - if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.Scene.name == scene.name) + if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.Scene.Scene.name == scene.name) { SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded; @@ -278,14 +282,14 @@ private void AddJobToQueue(QueuedSceneJob queuedSceneJob) /// /// Server always loads like it normally would /// - public AsyncOperation GenericLoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) + public AsyncOperationHandle GenericLoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) { m_ServerSceneBeingLoaded = sceneName; if (NetcodeIntegrationTest.IsRunning) { SceneManager.sceneLoaded += Sever_SceneLoaded; } - var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode); + var operation = Addressables.LoadSceneAsync(sceneName, loadSceneMode); sceneEventProgress.SetAsyncOperation(operation); return operation; } @@ -302,30 +306,30 @@ private void Sever_SceneLoaded(Scene scene, LoadSceneMode arg1) /// /// Server always unloads like it normally would /// - public AsyncOperation GenericUnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress) + public AsyncOperationHandle GenericUnloadSceneAsync(SceneInstance scene, SceneEventProgress sceneEventProgress) { - var operation = SceneManager.UnloadSceneAsync(scene); + var operation = Addressables.UnloadSceneAsync(scene); sceneEventProgress.SetAsyncOperation(operation); return operation; } - public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) + public AsyncOperationHandle LoadSceneAsync(string sceneAssetKey, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) { // Server and non NetcodeIntegrationTest tests use the generic load scene method if (!NetcodeIntegrationTest.IsRunning) { - return GenericLoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); + return GenericLoadSceneAsync(sceneAssetKey, loadSceneMode, sceneEventProgress); } else // NetcodeIntegrationTest Clients always get added to the jobs queue { - AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, SceneName = sceneName, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Loading }); + AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, SceneName = sceneAssetKey, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Loading }); } - return null; + return default; } - public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress) + public AsyncOperationHandle UnloadSceneAsync(SceneInstance scene, SceneEventProgress sceneEventProgress) { // Server and non NetcodeIntegrationTest tests use the generic unload scene method if (!NetcodeIntegrationTest.IsRunning) @@ -337,7 +341,7 @@ public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEven AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Unloading }); } // This is OK to return a "nothing" AsyncOperation since we are simulating client loading - return null; + return default; } /// @@ -379,7 +383,7 @@ internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName) { continue; } - NetworkManager.SceneManager.ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded); + NetworkManager.SceneManager.ScenesLoaded.Add(sceneLoaded.handle, new NetworkSceneManager.SceneData(null, sceneLoaded)); StartTrackingScene(sceneLoaded, true, NetworkManager); return sceneLoaded; } @@ -592,7 +596,7 @@ public Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkMa return m_InvalidScene; } - public void PopulateLoadedScenes(ref Dictionary scenesLoaded, NetworkManager networkManager) + public void PopulateLoadedScenes(ref Dictionary scenesLoaded, NetworkManager networkManager) { if (!SceneNameToSceneHandles.ContainsKey(networkManager)) { @@ -630,7 +634,7 @@ public void PopulateLoadedScenes(ref Dictionary scenesLoaded, Networ SceneNameToSceneHandles[networkManager][scene.name].Add(scene.handle, sceneEntry); if (!scenesLoaded.ContainsKey(scene.handle)) { - scenesLoaded.Add(scene.handle, scene); + scenesLoaded.Add(scene.handle, new NetworkSceneManager.SceneData(null, scene)); } } else @@ -858,7 +862,7 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load if (!sceneManager.ScenesLoaded.ContainsKey(scene.handle)) { StartTrackingScene(scene, true, networkManager); - sceneManager.ScenesLoaded.Add(scene.handle, scene); + sceneManager.ScenesLoaded.Add(scene.handle, new NetworkSceneManager.SceneData(null, scene)); } } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index bf26f9f05a..58eaaf8fbb 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -408,7 +408,7 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan // with the clients. if (!networkManager.SceneManager.ScenesLoaded.ContainsKey(scene.handle)) { - networkManager.SceneManager.ScenesLoaded.Add(scene.handle, scene); + networkManager.SceneManager.ScenesLoaded.Add(scene.handle, new NetworkSceneManager.SceneData(null, scene)); } networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef b/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef index 2fb107c79f..14fad83862 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef @@ -6,7 +6,9 @@ "Unity.Multiplayer.MetricTypes", "Unity.Multiplayer.NetStats", "Unity.Multiplayer.Tools.MetricTypes", - "Unity.Multiplayer.Tools.NetStats" + "Unity.Multiplayer.Tools.NetStats", + "Unity.ResourceManager", + "Unity.Addressables" ], "optionalUnityReferences": [ "TestAssemblies" From 36625fa4d47f743221be0a54a330b3bb36f85393 Mon Sep 17 00:00:00 2001 From: Kenneth Roecks Date: Fri, 10 May 2024 16:38:25 -0700 Subject: [PATCH 086/236] pending update --- .../Runtime/Messaging/CustomMessageManager.cs | 2 ++ .../Messaging/Messages/UnnamedMessage.cs | 3 +++ .../Runtime/Messaging/NetworkMessageManager.cs | 3 +++ .../SceneManagement/NetworkSceneManager.cs | 17 +++++++++++++++++ 4 files changed, 25 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index 620e5bb48b..18cb6417f8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -32,6 +32,8 @@ internal CustomMessagingManager(NetworkManager networkManager) internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize) { + Debug.Log($"CustomMessageManager::InvokeUnnamedMessage [CUSTOM] {clientId} {reader.Length} {serializedHeaderSize}"); + if (OnUnnamedMessage != null) { var pos = reader.Position; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs index ccce67c3f5..28ec94d6e7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs @@ -1,3 +1,5 @@ +using UnityEngine; + namespace Unity.Netcode { internal struct UnnamedMessage : INetworkMessage @@ -20,6 +22,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { + Debug.Log($"UnnamedMessage::Handle [CUSTOM] {context.MessageSize}"); ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 890664da58..0b439f127c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -558,6 +558,7 @@ internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = fals public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, NetworkMessageManager manager) where T : INetworkMessage, new() { + // Debug.Log($"NetworkMessageManager::ReceiveMessage {reader.Length} - {context.SenderId} {context.MessageSize}"); var message = new T(); var messageVersion = 0; // Special cases because these are the messages that carry the version info - thus the version info isn't @@ -640,6 +641,8 @@ internal int SendMessage(ref TMessageType messa message.Serialize(tmpSerializer, messageVersion); + // Debug.Log($"Sending [CUSTOM] message with {delivery} - Size: {tmpSerializer.Position}"); + var size = SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds, messageVersion); largestSerializedSize = size > largestSerializedSize ? size : largestSerializedSize; } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 51653fd75e..f37fee247e 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1411,6 +1411,14 @@ public SceneEventProgress LoadAddressableScene(AssetReference sceneReference, Lo return sceneEventProgress; } + + private static Dictionary s_ResourceLocationsBySceneName = new(); + + public bool PrepareToLoadScene(string sceneName, Action loaded) + { + return false; + } + /// /// Server side: /// Loads the scene name in either additive or single loading mode. @@ -1421,6 +1429,15 @@ public SceneEventProgress LoadAddressableScene(AssetReference sceneReference, Lo /// ( means it was successful) public SceneEventProgress LoadScene(string sceneName, LoadSceneMode loadSceneMode) { + if (!s_ResourceLocationsBySceneName.TryGetValue(sceneName, out var found)) + { + var resourceLocationAsync = Addressables.LoadResourceLocationsAsync(sceneName); + if (!resourceLocationAsync.IsValid()) + { + return null; + } + } + var sceneEventProgress = ValidateSceneEventLoading(sceneName); if (sceneEventProgress.Status != SceneEventProgressStatus.Started) { From 8d7b51bb14631afbee54a89868db579810ca0eb1 Mon Sep 17 00:00:00 2001 From: Kenneth Roecks Date: Fri, 10 May 2024 16:48:01 -0700 Subject: [PATCH 087/236] local trollking changes --- .../Runtime/Configuration/NetworkConfig.cs | 2 +- .../Runtime/Configuration/NetworkPrefabs.cs | 21 +++- .../Runtime/Core/NetworkLogScope.cs | 103 ++++++++++++++++++ .../Runtime/Core/NetworkLogScope.cs.meta | 3 + .../Runtime/Core/NetworkManager.cs | 2 +- .../Runtime/Core/NetworkObject.cs | 11 +- .../Runtime/Messaging/CustomMessageManager.cs | 11 +- .../Messages/ConnectionRequestMessage.cs | 2 +- .../Messaging/Messages/CreateObjectMessage.cs | 5 +- .../Messaging/Messages/UnnamedMessage.cs | 2 +- .../SceneManagement/NetworkSceneManager.cs | 49 ++++++--- .../SceneManagement/SceneEventProgress.cs | 1 + .../Runtime/Spawning/NetworkPrefabHandler.cs | 17 ++- .../Runtime/Spawning/NetworkSpawnManager.cs | 18 ++- .../Bootstrap/Prefabs/BootstrapPlayer.prefab | 28 ++++- .../Runtime/IntegrationTestSceneHandler.cs | 2 +- 16 files changed, 239 insertions(+), 38 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 9b855ec5ba..9f90bedebb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -238,8 +238,8 @@ public ulong GetConfig(bool cache = true) { var sortedDictionary = Prefabs.NetworkPrefabOverrideLinks.OrderBy(x => x.Key); foreach (var sortedEntry in sortedDictionary) - { + Debug.Log($"[NetworkConfig] - GetConfig - [{sortedEntry.Key}={sortedEntry.Value.Prefab}]"); writer.WriteValueSafe(sortedEntry.Key); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index 42758f7e78..fa50231409 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using TrollKing.Core; using UnityEngine; namespace Unity.Netcode @@ -13,6 +14,8 @@ namespace Unity.Netcode [Serializable] public class NetworkPrefabs { + private static readonly NetworkLogScope k_Log = new NetworkLogScope(nameof(NetworkPrefabs)); + /// /// Edit-time scripted object containing a list of NetworkPrefabs. /// @@ -49,6 +52,7 @@ public class NetworkPrefabs private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) { + k_Log.Debug(() => $"NetworkPrefabs AddTriggeredByNetworkPrefabList [networkPrefab={networkPrefab}]"); if (AddPrefabRegistration(networkPrefab)) { // Don't add this to m_RuntimeAddedPrefabs @@ -59,6 +63,7 @@ private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) { + k_Log.Debug(() => $"NetworkPrefabs RemoveTriggeredByNetworkPrefabList [networkPrefab={networkPrefab}]"); m_Prefabs.Remove(networkPrefab); } @@ -86,6 +91,7 @@ internal void Shutdown() /// public void Initialize(bool warnInvalid = true) { + k_Log.Debug(() => $"NetworkPrefabs Initialize [warnInvalid={warnInvalid}]"); m_Prefabs.Clear(); foreach (var list in NetworkPrefabsLists) { @@ -104,6 +110,8 @@ public void Initialize(bool warnInvalid = true) { foreach (var networkPrefab in list.PrefabList) { + var netObj = networkPrefab.Prefab.GetComponent(); + k_Log.Debug(() => $"NetworkPrefabs Add networkPrefab [networkPrefab={networkPrefab}] [prefab={networkPrefab.Prefab}] [prefabHash={netObj.PrefabIdHash}] [globalHash={netObj.GlobalObjectIdHash}]"); prefabs.Add(networkPrefab); } } @@ -277,9 +285,19 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab) { return false; } + + var netObj = networkPrefab.Prefab.GetComponent(); + if (netObj) + { + k_Log.Debug(() => $"NetworkPrefabs AddPrefabRegistration [prefab={networkPrefab.Prefab.name}] [networkPrefab={networkPrefab}] [hash={netObj.PrefabIdHash}] [global={netObj.GlobalObjectIdHash}]"); + } + + + // Safeguard validation check since this method is called from outside of NetworkConfig and we can't control what's passed in. if (!networkPrefab.Validate()) { + Debug.LogError($"NetworkPrefabs AddPrefabRegistration INVALID [networkPrefab={networkPrefab}]"); return false; } @@ -292,7 +310,7 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab) var networkObject = networkPrefab.Prefab.GetComponent(); // This should never happen, but in the case it somehow does log an error and remove the duplicate entry - Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); + Debug.LogError($"NetworkPrefabs {nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); return false; } @@ -300,6 +318,7 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab) if (networkPrefab.Override == NetworkPrefabOverride.None) { NetworkPrefabOverrideLinks.Add(source, networkPrefab); + k_Log.Debug(() => $"NetworkPrefabs AddPrefabRegistration NetworkPrefabOverrideLinks [prefab={networkPrefab.Prefab.name}] [source={source}] [networkPrefab={networkPrefab}]"); return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs new file mode 100644 index 0000000000..a7fae61e3f --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs @@ -0,0 +1,103 @@ +using System; + +namespace TrollKing.Core +{ + public enum NetworkLoggingLevel + { + Debug, + Info, + Warn, + Error, + Exception, + None + } + + public class NetworkLogScope + { + private readonly string m_LoggerName; + private readonly NetworkLoggingLevel m_Level = NetworkLoggingLevel.Info; + + public NetworkLogScope(string logName, NetworkLoggingLevel logLevel = NetworkLoggingLevel.Info) + { + m_LoggerName = logName; + m_Level = logLevel; + } + + public NetworkLoggingLevel GetLevel() + { + return m_Level; + } + + public void Log(Func stringProvider, NetworkLoggingLevel logLevel = NetworkLoggingLevel.Info) + { + if (logLevel >= m_Level) + { + string logString = stringProvider.Invoke(); + DateTime time = DateTime.Now; + var shortTime = time.ToString("T"); + + switch (logLevel) + { + case NetworkLoggingLevel.Debug: + UnityEngine.Debug.Log($"[{shortTime}][DEBUG][{m_LoggerName}] {logString}"); + break; + case NetworkLoggingLevel.Info: + UnityEngine.Debug.Log($"[{shortTime}][INFO][{m_LoggerName}] {logString}"); + break; + case NetworkLoggingLevel.Warn: + UnityEngine.Debug.LogWarning($"[{shortTime}][WARN][{m_LoggerName}] {logString}"); + break; + case NetworkLoggingLevel.Error: + UnityEngine.Debug.LogError($"[{shortTime}][ERROR][{m_LoggerName}] {logString}"); + break; + case NetworkLoggingLevel.Exception: + UnityEngine.Debug.LogError($"[{shortTime}][EXCEPTION][{m_LoggerName}] {logString}"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null); + } + } + } + + public void Debug(Func logString) + { + Log(logString, NetworkLoggingLevel.Debug); + } + + public void Info(Func logString) + { + Log(logString, NetworkLoggingLevel.Info); + } + + public void Warning(Func logString) + { + Log(logString, NetworkLoggingLevel.Warn); + } + + public void LogWarning(Func logString) + { + Log(logString, NetworkLoggingLevel.Warn); + } + + public void Error(Func logString) + { + Log(logString, NetworkLoggingLevel.Error); + } + + public void LogError(Func logString) + { + Log(logString, NetworkLoggingLevel.Error); + } + + public void LogException(Exception e) + { + UnityEngine.Debug.LogException(e); + } + + public void LogError(Exception e) + { + UnityEngine.Debug.LogError($"[{m_LoggerName}] {e}"); + UnityEngine.Debug.LogException(e); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs.meta b/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs.meta new file mode 100644 index 0000000000..ea3fdf85eb --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3b83aae632414713b8843141e3cea71b +timeCreated: 1695872236 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 2c0e7a228e..e1b2df0866 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1268,7 +1268,7 @@ private void OnApplicationQuit() } // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit - private void OnDestroy() + public virtual void OnDestroy() { ShutdownInternal(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 1fbba62511..5591235b0b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -26,6 +26,11 @@ public sealed class NetworkObject : MonoBehaviour [SerializeField] internal uint GlobalObjectIdHash; + public uint GetGlobalHash() + { + return GlobalObjectIdHash; + } + /// /// Used to track the source GlobalObjectIdHash value of the associated network prefab. /// When an override exists or it is in-scene placed, GlobalObjectIdHash and PrefabGlobalObjectIdHash @@ -34,7 +39,7 @@ public sealed class NetworkObject : MonoBehaviour internal uint PrefabGlobalObjectIdHash; /// - /// This is the source prefab of an in-scene placed NetworkObject. This is not set for in-scene + /// This is the source prefab of an in-scene placed NetworkObject. This is not set for in-scene /// placd NetworkObjects that are not prefab instances, dynamically spawned prefab instances, /// or for network prefab assets. /// @@ -176,8 +181,8 @@ private bool IsEditingPrefab() } /// - /// This checks to see if this NetworkObject is an in-scene placed prefab instance. If so it will - /// automatically find the source prefab asset's GlobalObjectIdHash value, assign it to + /// This checks to see if this NetworkObject is an in-scene placed prefab instance. If so it will + /// automatically find the source prefab asset's GlobalObjectIdHash value, assign it to /// InScenePlacedSourceGlobalObjectIdHash and mark this as being in-scene placed. /// /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index 18cb6417f8..c3a4a53267 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using TrollKing.Core; using Unity.Collections; using UnityEngine; @@ -11,13 +12,21 @@ namespace Unity.Netcode /// public class CustomMessagingManager { + private static readonly NetworkLogScope Log = new NetworkLogScope(nameof(CustomMessagingManager)); + private readonly NetworkManager m_NetworkManager; internal CustomMessagingManager(NetworkManager networkManager) { + Log.Info(() => $"Constructor"); m_NetworkManager = networkManager; } + ~CustomMessagingManager() + { + Log.Info(() => $"Deconstructor"); + } + /// /// Delegate used for incoming unnamed messages /// @@ -32,7 +41,7 @@ internal CustomMessagingManager(NetworkManager networkManager) internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize) { - Debug.Log($"CustomMessageManager::InvokeUnnamedMessage [CUSTOM] {clientId} {reader.Length} {serializedHeaderSize}"); + // Debug.Log($"CustomMessageManager::InvokeUnnamedMessage [CUSTOM] {clientId} {reader.Length} {serializedHeaderSize}"); if (OnUnnamedMessage != null) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index 8d558bf202..c71b612422 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -92,7 +92,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match"); + NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. IncomingHash=[{ConfigHash}] OurHash=[{networkManager.NetworkConfig.GetConfig()}] The configuration between the server and client does not match"); } networkManager.DisconnectClient(context.SenderId); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 809b7c92f4..482eb16b47 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -1,4 +1,6 @@ using System.Runtime.CompilerServices; +using UnityEngine; + namespace Unity.Netcode { internal struct CreateObjectMessage : INetworkMessage @@ -10,6 +12,7 @@ internal struct CreateObjectMessage : INetworkMessage public void Serialize(FastBufferWriter writer, int targetVersion) { + // Debug.Log($"CreateObjectMessage Serialize [v={targetVersion}] [H={ObjectInfo.Hash}] [id={ObjectInfo.NetworkObjectId}]"); ObjectInfo.Serialize(writer); } @@ -28,7 +31,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int return false; } m_ReceivedNetworkVariableData = reader; - + // Debug.Log($"CreateObjectMessage Deserialize [v={receivedMessageVersion}] [H={ObjectInfo.Hash}] [id={ObjectInfo.NetworkObjectId}]"); return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs index 28ec94d6e7..c044e1d69b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs @@ -22,7 +22,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { - Debug.Log($"UnnamedMessage::Handle [CUSTOM] {context.MessageSize}"); + // Debug.Log($"UnnamedMessage::Handle [CUSTOM] {context.MessageSize}"); ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize); } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index f37fee247e..12e80c66c5 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using TrollKing.Core; using Unity.Collections; using UnityEngine; using UnityEngine.AddressableAssets; @@ -135,6 +136,8 @@ public class SceneEvent /// public class NetworkSceneManager : IDisposable { + private static readonly NetworkLogScope Log = new NetworkLogScope(nameof(NetworkSceneManager)); + private const NetworkDelivery k_DeliveryType = NetworkDelivery.ReliableFragmentedSequenced; internal const int InvalidSceneNameOrPath = -1; @@ -747,23 +750,26 @@ private void SceneManager_ActiveSceneChanged(Scene current, Scene next) /// true (Valid) or false (Invalid) internal bool ValidateSceneBeforeLoading(string sceneName, LoadSceneMode loadSceneMode) { - var validated = true; - var sceneIndex = SceneUtility.GetBuildIndexByScenePath(sceneName); - if (VerifySceneBeforeLoading != null) - { - validated = VerifySceneBeforeLoading.Invoke(sceneIndex, sceneName, loadSceneMode); - } - if (!validated && !m_DisableValidationWarningMessages) - { - var serverHostorClient = "Client"; - if (NetworkManager.IsServer) - { - serverHostorClient = NetworkManager.IsHost ? "Host" : "Server"; - } + Log.Debug(() => $"ValidateSceneBeforeLoading {sceneName} {loadSceneMode}"); + return true; - Debug.LogWarning($"Scene {sceneName} of Scenes in Build Index {sceneIndex} being loaded in {loadSceneMode} mode failed validation on the {serverHostorClient}!"); - } - return validated; + // var validated = true; + // var sceneIndex = SceneUtility.GetBuildIndexByScenePath(sceneName); + // if (VerifySceneBeforeLoading != null) + // { + // validated = VerifySceneBeforeLoading.Invoke(sceneIndex, sceneName, loadSceneMode); + // } + // if (!validated && !m_DisableValidationWarningMessages) + // { + // var serverHostorClient = "Client"; + // if (NetworkManager.IsServer) + // { + // serverHostorClient = NetworkManager.IsHost ? "Host" : "Server"; + // } + // + // Debug.LogWarning($"Scene {sceneName} of Scenes in Build Index {sceneIndex} being loaded in {loadSceneMode} mode failed validation on the {serverHostorClient}!"); + // } + // return validated; } /// @@ -1429,6 +1435,7 @@ public bool PrepareToLoadScene(string sceneName, Action loaded) /// ( means it was successful) public SceneEventProgress LoadScene(string sceneName, LoadSceneMode loadSceneMode) { + // Debug.Log($"[NetworkSceneManager] LoadScene sceneName={sceneName}"); if (!s_ResourceLocationsBySceneName.TryGetValue(sceneName, out var found)) { var resourceLocationAsync = Addressables.LoadResourceLocationsAsync(sceneName); @@ -1438,6 +1445,7 @@ public SceneEventProgress LoadScene(string sceneName, LoadSceneMode loadSceneMod } } + // Debug.Log($"[NetworkSceneManager] LoadScene Finished LoadResources sceneName={sceneName}"); var sceneEventProgress = ValidateSceneEventLoading(sceneName); if (sceneEventProgress.Status != SceneEventProgressStatus.Started) { @@ -1489,7 +1497,9 @@ public SceneEventProgress LoadScene(string sceneName, LoadSceneMode loadSceneMod // Now start loading the scene sceneEventProgress.SceneEventId = sceneEventId; sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded; + // Debug.Log($"[NetworkSceneManager] BEGIN SceneManagerHandler.LoadSceneAsync sceneName={sceneName} loadSceneMode={loadSceneMode}"); var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); + // Debug.Log($"[NetworkSceneManager] END SceneManagerHandler.LoadSceneAsync sceneName={sceneName} loadSceneMode={loadSceneMode}"); // Notify the local server that a scene loading event has begun OnSceneEvent?.Invoke(new SceneEvent() { @@ -1658,7 +1668,10 @@ private void OnClientSceneLoadingEvent(uint sceneEventId) SceneEventId = sceneEventId, OnSceneEventCompleted = OnSceneLoaded }; + + // Debug.Log($"[NetworkSceneManager] BEGIN LoadSceneAsync on SceneManagerHandler={SceneManagerHandler.GetType()}"); var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress); + // Debug.Log($"[NetworkSceneManager] END LoadSceneAsync on SceneManagerHandler={SceneManagerHandler.GetType()}"); OnSceneEvent?.Invoke(new SceneEvent() { @@ -1736,7 +1749,7 @@ private void OnSceneLoaded(uint sceneEventId, string loadedSceneName) /// private void OnServerLoadedScene(uint sceneEventId, Scene scene) { - Debug.Log($"NetworkSceneManager - OnServerLoadedScene eventId:{sceneEventId} scene:{scene.name}"); + // Debug.Log($"NetworkSceneManager - OnServerLoadedScene eventId:{sceneEventId} scene:{scene.name}"); var sceneEventData = SceneEventDataStore[sceneEventId]; // Register in-scene placed NetworkObjects with spawn manager @@ -1987,7 +2000,9 @@ private void OnClientBeginSync(uint sceneEventId) SceneEventId = sceneEventId, OnSceneEventCompleted = ClientLoadedSynchronization }; + // Debug.Log($"[NetworkSceneManager] OnClientBeginSync BEGIN SceneManagerHandler.LoadSceneAsync sceneName={sceneName} loadSceneMode={loadSceneMode}"); sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); + // Debug.Log($"[NetworkSceneManager] OnClientBeginSync END SceneManagerHandler.LoadSceneAsync sceneName={sceneName} loadSceneMode={loadSceneMode}"); // Notify local client that a scene load has begun OnSceneEvent?.Invoke(new SceneEvent() diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index f87c2f538b..ebac76558a 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using UnityEngine; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index bb0afc4f3a..90358543eb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using TrollKing.Core; using UnityEngine; namespace Unity.Netcode @@ -50,6 +51,8 @@ public interface INetworkPrefabInstanceHandler /// public class NetworkPrefabHandler { + private static readonly NetworkLogScope k_Log = new NetworkLogScope(nameof(NetworkPrefabHandler)); + private NetworkManager m_NetworkManager; /// @@ -315,7 +318,12 @@ public GameObject GetNetworkPrefabOverride(GameObject gameObject) case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Prefab: { - return m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab; + var res = m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab; + k_Log.Debug(() => $"NetworkPrefabHandler GetNetworkPrefabOverride [gameObject={gameObject.name}] [networkPrefab={networkObject.GlobalObjectIdHash}]" + + $"[overrideType={m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].Override}]" + + $"[overrideObj={m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab}]"); + + return res; } } } @@ -360,12 +368,18 @@ public void AddNetworkPrefab(GameObject prefab) var networkPrefab = new NetworkPrefab { Prefab = prefab }; bool added = m_NetworkManager.NetworkConfig.Prefabs.Add(networkPrefab); + k_Log.Debug(() => $"NetworkPrefabHandler AddNetworkPrefab prefab={prefab.name} hash={networkObject.GlobalObjectIdHash}"); if (m_NetworkManager.IsListening && added) { m_NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash); } } + public IReadOnlyList GetPrefabs() + { + return m_NetworkManager.NetworkConfig.Prefabs.Prefabs; + } + /// /// Remove a prefab from the prefab list. /// As with AddNetworkPrefab, this is specific to the client it's called on - @@ -405,6 +419,7 @@ internal void RegisterPlayerPrefab() //In the event there is no NetworkPrefab entry (i.e. no override for default player prefab) if (!networkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject.GlobalObjectIdHash)) { + k_Log.Debug(() => $"[NetworkPrefabHandler] RegisterPlayerPrefab - PlayerPrefab={networkConfig.PlayerPrefab.name} hash={playerPrefabNetworkObject.GlobalObjectIdHash}"); //Then add a new entry for the player prefab AddNetworkPrefab(networkConfig.PlayerPrefab); } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 769cf61f51..0876e07801 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using TrollKing.Core; using UnityEngine; namespace Unity.Netcode @@ -10,6 +11,8 @@ namespace Unity.Netcode /// public class NetworkSpawnManager { + private static readonly NetworkLogScope k_Log = new NetworkLogScope(nameof(NetworkSpawnManager)); + // Stores the objects that need to be shown at end-of-frame internal Dictionary> ObjectsToShowToClient = new Dictionary>(); @@ -414,7 +417,7 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ } /// - /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the + /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the /// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned. /// internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3 position = default, Quaternion rotation = default, bool isScenePlaced = false) @@ -446,8 +449,8 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Prefab: { - // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the - // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically + // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the + // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically // but might want to use the same source network prefab as an in-scene placed NetworkObject. // (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically // spawning them so the original prefab placed is preserved and this is not needed) @@ -679,6 +682,9 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong return; } + k_Log.Debug(() => $"[NetworkSpawnManager] NetworkPrefab SpawnNetworkObjectLocallyCommon networkObject={networkObject.name} hash={networkObject.GlobalObjectIdHash} prefabHash={networkObject.PrefabIdHash}, networkId={networkId}, sceneObject={sceneObject}, playerObject={playerObject}, ownerClientId={ownerClientId}, destroyWithScene={destroyWithScene}"); + + networkObject.IsSpawned = true; networkObject.IsSceneObject = sceneObject; @@ -778,6 +784,8 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong { networkObject.PrefabGlobalObjectIdHash = networkObject.InScenePlacedSourceGlobalObjectIdHash; } + + k_Log.Debug(() => $"[NetworkSpawnManager] NetworkPrefab SpawnNetworkObjectLocallyCommon networkObject={networkObject.name} hash={networkObject.GlobalObjectIdHash} prefabHash={networkObject.PrefabIdHash}, networkId={networkId}, sceneObject={sceneObject}, playerObject={playerObject}, ownerClientId={ownerClientId}, destroyWithScene={destroyWithScene}"); } internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject) @@ -909,7 +917,7 @@ internal void DespawnAndDestroyNetworkObjects() } } - // If spawned, then despawn and potentially destroy. + // If spawned, then despawn and potentially destroy. if (networkObjects[i].IsSpawned) { OnDespawnObject(networkObjects[i], shouldDestroy); @@ -1107,7 +1115,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } /// - /// Updates all spawned for the specified newly connected client + /// Updates all spawned for the specified newly connected client /// Note: if the clientId is the server then it is observable to all spawned 's /// /// diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab b/com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab index 5b7b44713f..34fd335d94 100644 --- a/com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab +++ b/com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab @@ -29,12 +29,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 3439633038736912633} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &3439633038736913157 MeshFilter: @@ -55,11 +56,15 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -93,9 +98,17 @@ SphereCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 3439633038736912633} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Radius: 0.5 m_Center: {x: 0, y: 0, z: 0} --- !u!114 &3439633038736912634 @@ -110,8 +123,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 951099334 + GlobalObjectIdHash: 2131729030 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 --- !u!114 &2776227185612554462 @@ -138,9 +155,12 @@ MonoBehaviour: PositionThreshold: 0 RotAngleThreshold: 0 ScaleThreshold: 0 + UseQuaternionSynchronization: 0 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 InLocalSpace: 0 Interpolate: 1 - FixedSendsPerSecond: 30 + SlerpPosition: 0 --- !u!114 &6046305264893698362 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index 010a1390bc..b54bec16aa 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -290,7 +290,7 @@ public AsyncOperationHandle GenericLoadSceneAsync(string sceneNam SceneManager.sceneLoaded += Sever_SceneLoaded; } var operation = Addressables.LoadSceneAsync(sceneName, loadSceneMode); - sceneEventProgress.SetAsyncOperation(operation); + // sceneEventProgress.SetAsyncOperation(operation); return operation; } From 640a7a3cdb1a535bce022834feefb0af73c85e1e Mon Sep 17 00:00:00 2001 From: Kenneth Roecks Date: Sat, 8 Jun 2024 16:14:22 -0700 Subject: [PATCH 088/236] minor change --- com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 5e4ce503c0..eab7d0f845 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -414,8 +414,8 @@ public NetworkManager NetworkManager get { if (NetworkObject?.NetworkManager != null) - { return NetworkObject?.NetworkManager; + { } return NetworkManager.Singleton; From 21cf55ea4ca36ad8be3b0f348dd5100c2fa678e4 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 31 Aug 2024 17:24:05 -0500 Subject: [PATCH 089/236] fix: allow the check for a NetworkObject component on NetworkBehaviours to be disabled (#3031) * update Adding additional configuration parameter, NetworkBehaviour-Check-For-NetworkObject, that provides user the ability to bypass the automatic check for a NetworkObject on a NetworkBehaviour (in editor only). * update Adding project settings UI for the added check for networkobject property. * update Exiting early if the check for NetworkObject setting is disabled. * update change log entry * update Disable and uncheck the Auto-Add NetworkObject Component when Check For NetworkObject Component is disabled. Enable Auto-Add NetworkObject Component when Check For NetworkObject Component is re-enabled. User can select if they want to re-check the Auto-Add NetworkObject Component at that point. * Update CHANGELOG.md --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ .../NetcodeForGameObjectsSettings.cs | 18 +++++++++++++- .../Configuration/NetcodeSettingsProvider.cs | 24 ++++++++++++++++--- .../Editor/NetworkBehaviourEditor.cs | 6 +++++ 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2a729d3e7a..8eebfffb3d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3031) + ### Fixed -Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030) diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs index 4fc4b0c69d..70dfc02021 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs @@ -5,6 +5,7 @@ namespace Unity.Netcode.Editor.Configuration internal class NetcodeForGameObjectsEditorSettings { internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist"; + internal const string CheckForNetworkObject = "NetworkBehaviour-Check-For-NetworkObject"; internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed"; internal static int GetNetcodeInstallMultiplayerToolTips() @@ -28,7 +29,7 @@ internal static bool GetAutoAddNetworkObjectSetting() { return EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists); } - + // Default for this is false return false; } @@ -36,5 +37,20 @@ internal static void SetAutoAddNetworkObjectSetting(bool autoAddSetting) { EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting); } + + internal static bool GetCheckForNetworkObjectSetting() + { + if (EditorPrefs.HasKey(CheckForNetworkObject)) + { + return EditorPrefs.GetBool(CheckForNetworkObject); + } + // Default for this is true + return true; + } + + internal static void SetCheckForNetworkObjectSetting(bool checkForNetworkObject) + { + EditorPrefs.SetBool(CheckForNetworkObject, checkForNetworkObject); + } } } diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs index 8a814ac6d6..26b12175dd 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs @@ -81,6 +81,7 @@ private static void OnDeactivate() internal static NetcodeSettingsLabel NetworkObjectsSectionLabel; internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle; + internal static NetcodeSettingsToggle CheckForNetworkObjectToggle; internal static NetcodeSettingsLabel MultiplayerToolsLabel; internal static NetcodeSettingsToggle MultiplayerToolTipStatusToggle; @@ -103,6 +104,11 @@ private static void CheckForInitialize() AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObject Component", "When enabled, NetworkObject components are automatically added to GameObjects when NetworkBehaviour components are added first.", 20); } + if (CheckForNetworkObjectToggle == null) + { + CheckForNetworkObjectToggle = new NetcodeSettingsToggle("Check for NetworkObject Component", "When disabled, the automatic check on NetworkBehaviours for an associated NetworkObject component will not be performed and Auto-Add NetworkObject Component will be disabled.", 20); + } + if (MultiplayerToolsLabel == null) { MultiplayerToolsLabel = new NetcodeSettingsLabel("Multiplayer Tools", 20); @@ -120,7 +126,9 @@ private static void OnGuiHandler(string obj) CheckForInitialize(); var autoAddNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting(); + var checkForNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetCheckForNetworkObjectSetting(); var multiplayerToolsTipStatus = NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() == 0; + var settings = NetcodeForGameObjectsProjectSettings.instance; var generateDefaultPrefabs = settings.GenerateDefaultNetworkPrefabs; var networkPrefabsPath = settings.TempNetworkPrefabsPath; @@ -134,7 +142,13 @@ private static void OnGuiHandler(string obj) { GUILayout.BeginVertical("Box"); NetworkObjectsSectionLabel.DrawLabel(); - autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting); + + autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting, checkForNetworkObjectSetting); + checkForNetworkObjectSetting = CheckForNetworkObjectToggle.DrawToggle(checkForNetworkObjectSetting); + if (autoAddNetworkObjectSetting && !checkForNetworkObjectSetting) + { + autoAddNetworkObjectSetting = false; + } GUILayout.EndVertical(); GUILayout.BeginVertical("Box"); @@ -184,6 +198,7 @@ private static void OnGuiHandler(string obj) if (EditorGUI.EndChangeCheck()) { NetcodeForGameObjectsEditorSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting); + NetcodeForGameObjectsEditorSettings.SetCheckForNetworkObjectSetting(checkForNetworkObjectSetting); NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1); settings.GenerateDefaultNetworkPrefabs = generateDefaultPrefabs; settings.TempNetworkPrefabsPath = networkPrefabsPath; @@ -213,10 +228,13 @@ internal class NetcodeSettingsToggle : NetcodeGUISettings { private GUIContent m_ToggleContent; - public bool DrawToggle(bool currentSetting) + public bool DrawToggle(bool currentSetting, bool enabled = true) { EditorGUIUtility.labelWidth = m_LabelSize; - return EditorGUILayout.Toggle(m_ToggleContent, currentSetting, m_LayoutWidth); + GUI.enabled = enabled; + var returnValue = EditorGUILayout.Toggle(m_ToggleContent, currentSetting, m_LayoutWidth); + GUI.enabled = true; + return returnValue; } public NetcodeSettingsToggle(string labelText, string toolTip, float layoutOffset) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index 7d57afb055..c98870c591 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -352,6 +352,12 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje return; } + // If this automatic check is disabled, then do not perform this check. + if (!NetcodeForGameObjectsEditorSettings.GetCheckForNetworkObjectSetting()) + { + return; + } + // Now get the root parent transform to the current GameObject (or itself) var rootTransform = GetRootParentTransform(gameObject.transform); if (!rootTransform.TryGetComponent(out var networkManager)) From aee018c13846f1a35753974796e8c959a052103d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 5 Sep 2024 10:12:20 -0500 Subject: [PATCH 090/236] fix: NetworkTransform initial synchronization, parenting, smooth transitions between transform spaces, and UI updates (#3013) * fix This fixes a synchronization issue with owner authoritative instances when running in client-server mode and parenting. When the server sends a parent sync message back with the position of the client's owned object it is out of sync with the client's position by the time the message is received. This fix gives users the option to not synchronize the transform position of a NetworkObject when using an owner authoritative motion mode. * fix This fix provides users with the option to have child `NetworkTransform`(s) tick synchronized with their parent. When the parent has `TickSyncChildren` enabled, any child `NetworkTransform`(s) that the local client has authority over will force a synchronization of their transform state. When a client is not the authority of the parent but it is the authority of one or more children, then when the parent sends a state update it will force the child/children the client has authority over to send a state update. Children will still send state updates if the transform has a delta that exceeds an axis threshold and the parent has yet to send a synchronization, but if a parent sends a synchronization within the same tick period the child/children will still send another state update. * wip Better approach, but still flawed. I think converting any remaining items in the interpolators when the transform space changes will yield consistent results. * fix Seamless transform space transitions when parenting. * update We no longer need to send lossy scale since we defer applying the initial synchronization data until the non-authority side has finished synchronizing. * fix Fixing some minor issues with nested `NetworkTransforms` (i.e. under the same `NetworkObject`) and adding additional comments. Reverting back to just checking `InLocalSpace` (as opposed to the interpolator's) when applying a position on the non-authority side. * test fix Fixing some issues with `NestedNetworkTransformTests` assets to account for some past updates. Fixing an issue with `NetworkTransformTests` where it needs to not just advance the tick (for updates) but needs to also advance to the next tick. * fix caught scenario where `NetworkObject.NetworkTransforms` could not be set properly. * fix This fixes a minor "blip" when transitioning from world to local space, * test disabling two tests that need to be reviewed by Kitty and/or re-written. * fix Updating all NetworkTransform instances that belong to a specific NetworkObject as opposed to just the root. Also not updating when not spawned or disabled NetworkTransforms. * test Making test asset adjustments for recent updates in NetworkTransform. * update Adding change log entry. * update mid-point check-in * test Updating interpolation test to recent NetworkTransform updates. * update wip towards getting a cleaner divide between the two modes. * update Some adjustments to when nested children check their own state updates or exit early. * update Cleaning up some Rigidbody references and making Rigidbody & Rigidbody2D accessible via NetworkRigidbody and NetworkRigidbody2D. * update Adding an internal InternalOnNetworkObjectParentChanged method that gets invoked ahead of OnNetworkObjectParentChanged so user code can't override any NGO components that need this kind of notification. * update Adding InternalOnNetworkSessionSynchronized to be invoked by NGO components to assure they don't get overridden by user script. * update Renaming some internal properties that are common names and would conflict with serialization if a user defined them in a derived class. Making sure both the position and rotation interpolators are configured for the transform space when reset. Adding the ability to change the parent locally when running with a client-server network topology. Adding SwitchTransformSpaceWhenParented property to the NetworkTransformEditor. Shifting all of the non-authority's final synchronization logic to occur either once a client's initial connection synchronization is complete or when all of the NetworkObject's components have run through the spawn process. * update Adding some improved and needed UI updates to NetworkBehaviour derived components and NetworkManager. This includes a new NetcodeEditorBase class that allows users to more easily create customized, multi-generation, components that have their properties organized based on the generation of each child. * fix Wrapping the NetworkRigidbodyBaseEditor within physics defines. * style >.< (sigh) null checks can be simplified style/standards update. * update switching from TT as a class to TT as a MonoBehaviour. * fix This fixes an issue where a server instance parenting with AllowOwnerToParent set would send to itself. Adding check within the NetworkObject parenting logic to pass through to the server portion of sending the parenting message. * test adding some validation tests for the updates made in #3013 * update adding changelog entries * fix This fixes the two failing tests within NetworkTransformAnticipationTest and updates AnticipatedNetworkTransform to account for #3013's updates/changes. * fix Removing the added base.OnInspectorGUI() call (was causing duplicated properties) * update Assure the callouts and call-to-action buttons show up outside of the Foldout Group when extending NetworkManager. Assure that the NetworkManager has no Foldout Group when it is not extended. --- com.unity.netcode.gameobjects/CHANGELOG.md | 14 +- .../Editor/NetcodeEditorBase.cs | 62 ++ .../Editor/NetcodeEditorBase.cs.meta | 2 + .../Editor/NetworkBehaviourEditor.cs | 3 +- .../Editor/NetworkManagerEditor.cs | 85 +-- .../Editor/NetworkRigidbodyBaseEditor.cs | 42 ++ .../Editor/NetworkRigidbodyBaseEditor.cs.meta | 2 + .../Editor/NetworkTransformEditor.cs | 35 +- .../Components/AnticipatedNetworkTransform.cs | 79 ++- .../BufferedLinearInterpolator.cs | 85 ++- .../Components/NetworkRigidBodyBase.cs | 148 +++-- .../Runtime/Components/NetworkRigidbody.cs | 3 + .../Runtime/Components/NetworkRigidbody2D.cs | 1 + .../Runtime/Components/NetworkTransform.cs | 617 +++++++++++++----- .../Runtime/Core/NetworkBehaviour.cs | 14 + .../Runtime/Core/NetworkManager.cs | 20 + .../Runtime/Core/NetworkObject.cs | 32 +- .../Messaging/Messages/ParentSyncMessage.cs | 30 +- .../NetworkTransform/NetworkTransformBase.cs | 42 +- .../NetworkTransform/NetworkTransformTests.cs | 220 +++++++ .../NetworkTransformAnticipationTests.cs | 17 +- .../Runtime/TransformInterpolationTests.cs | 18 +- .../AutomatedPlayer-SA.prefab | 105 ++- .../NestedNetworkTransforms/ChildMover.cs | 7 +- .../NestedNetworkTransformTests.cs | 54 +- 25 files changed, 1363 insertions(+), 374 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs create mode 100644 com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs.meta create mode 100644 com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs create mode 100644 com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8eebfffb3d..47d74d92fd 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,14 +11,26 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3031) +- Added `NetworkTransform.SwitchTransformSpaceWhenParented` property that, when enabled, will handle the world to local, local to world, and local to local transform space transitions when interpolation is enabled. (#3013) +- Added `NetworkTransform.TickSyncChildren` that, when enabled, will tick synchronize nested and/or child `NetworkTransform` components to eliminate any potential visual jittering that could occur if the `NetworkTransform` instances get into a state where their state updates are landing on different network ticks. (#3013) +- Added `NetworkObject.AllowOwnerToParent` property to provide the ability to allow clients to parent owned objects when running in a client-server network topology. (#3013) +- Added `NetworkObject.SyncOwnerTransformWhenParented` property to provide a way to disable applying the server's transform information in the parenting message on the client owner instance which can be useful for owner authoritative motion models. (#3013) +- Added `NetcodeEditorBase` editor helper class to provide easier modification and extension of the SDK's components. (#3013) ### Fixed --Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030) +- Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030) - Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026) +- Fixed issue with newly/late joined clients and `NetworkTransform` synchronization of parented `NetworkObject` instances. (#3013) +- Fixed issue with smooth transitions between transform spaces when interpolation is enabled (requires `NetworkTransform.SwitchTransformSpaceWhenParented` to be enabled). (#3013) ### Changed +- Changed `NetworkTransformEditor` so it now derives from `NetcodeEditorBase`. (#3013) +- Changed `NetworkRigidbodyBaseEditor` so it now derives from `NetcodeEditorBase`. (#3013) +- Changed `NetworkManagerEditor` so it now derives from `NetcodeEditorBase`. (#3013) + + ## [2.0.0-pre.4] - 2024-08-21 ### Added diff --git a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs new file mode 100644 index 0000000000..5112065e80 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs @@ -0,0 +1,62 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace Unity.Netcode.Editor +{ + /// + /// The base Netcode Editor helper class to display derived based components
+ /// where each child generation's properties will be displayed within a FoldoutHeaderGroup. + ///
+ [CanEditMultipleObjects] + public partial class NetcodeEditorBase : UnityEditor.Editor where TT : MonoBehaviour + { + /// + public virtual void OnEnable() + { + } + + /// + /// Helper method to draw the properties of the specified child type component within a FoldoutHeaderGroup. + /// + /// The specific child type that should have its properties drawn. + /// The component type of the . + /// The to invoke that will draw the type properties. + /// The current expanded property value + /// The invoked to apply the updated value. + protected void DrawFoldOutGroup(Type type, Action displayProperties, bool expanded, Action setExpandedProperty) + { + var baseClass = target as TT; + EditorGUI.BeginChangeCheck(); + serializedObject.Update(); + var currentClass = typeof(T); + if (type.IsSubclassOf(currentClass) || (!type.IsSubclassOf(currentClass) && currentClass.IsSubclassOf(typeof(TT)))) + { + var expandedValue = EditorGUILayout.BeginFoldoutHeaderGroup(expanded, $"{currentClass.Name} Properties"); + if (expandedValue) + { + EditorGUILayout.EndFoldoutHeaderGroup(); + displayProperties.Invoke(); + } + else + { + EditorGUILayout.EndFoldoutHeaderGroup(); + } + EditorGUILayout.Space(); + setExpandedProperty.Invoke(expandedValue); + } + else + { + displayProperties.Invoke(); + } + serializedObject.ApplyModifiedProperties(); + EditorGUI.EndChangeCheck(); + } + + /// + public override void OnInspectorGUI() + { + serializedObject.ApplyModifiedProperties(); + } + } +} diff --git a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs.meta b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs.meta new file mode 100644 index 0000000000..25f0b07e38 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4ce97256a2d80f94bb340e13c71a24b8 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index c98870c591..1312ff69e6 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -301,9 +301,8 @@ public override void OnInspectorGUI() expanded = false; } - - serializedObject.ApplyModifiedProperties(); EditorGUI.EndChangeCheck(); + serializedObject.ApplyModifiedProperties(); } /// diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 0db3a653f7..5445b0d8e8 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -13,7 +13,7 @@ namespace Unity.Netcode.Editor /// [CustomEditor(typeof(NetworkManager), true)] [CanEditMultipleObjects] - public class NetworkManagerEditor : UnityEditor.Editor + public class NetworkManagerEditor : NetcodeEditorBase { private static GUIStyle s_CenteredWordWrappedLabelStyle; private static GUIStyle s_HelpBoxStyle; @@ -168,16 +168,8 @@ private void CheckNullProperties() .FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists)); } - /// - public override void OnInspectorGUI() + private void DisplayNetworkManagerProperties() { - Initialize(); - CheckNullProperties(); - -#if !MULTIPLAYER_TOOLS - DrawInstallMultiplayerToolsTip(); -#endif - if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) { serializedObject.Update(); @@ -298,48 +290,50 @@ public override void OnInspectorGUI() } serializedObject.ApplyModifiedProperties(); + } + } + private void DisplayCallToActionButtons() + { + if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) + { + string buttonDisabledReasonSuffix = ""; - // Start buttons below + if (!EditorApplication.isPlaying) { - string buttonDisabledReasonSuffix = ""; + buttonDisabledReasonSuffix = ". This can only be done in play mode"; + GUI.enabled = false; + } - if (!EditorApplication.isPlaying) + if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix))) { - buttonDisabledReasonSuffix = ". This can only be done in play mode"; - GUI.enabled = false; + m_NetworkManager.StartHost(); } - if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix))) { - if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartHost(); - } - - if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartServer(); - } + m_NetworkManager.StartServer(); + } - if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartClient(); - } + if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix))) + { + m_NetworkManager.StartClient(); } - else + } + else + { + if (GUILayout.Button(new GUIContent("Start Client", "Starts a distributed authority client instance" + buttonDisabledReasonSuffix))) { - if (GUILayout.Button(new GUIContent("Start Client", "Starts a distributed authority client instance" + buttonDisabledReasonSuffix))) - { - m_NetworkManager.StartClient(); - } + m_NetworkManager.StartClient(); } + } - if (!EditorApplication.isPlaying) - { - GUI.enabled = true; - } + if (!EditorApplication.isPlaying) + { + GUI.enabled = true; } } else @@ -368,6 +362,21 @@ public override void OnInspectorGUI() } } + /// + public override void OnInspectorGUI() + { + var networkManager = target as NetworkManager; + Initialize(); + CheckNullProperties(); +#if !MULTIPLAYER_TOOLS + DrawInstallMultiplayerToolsTip(); +#endif + void SetExpanded(bool expanded) { networkManager.NetworkManagerExpanded = expanded; }; + DrawFoldOutGroup(networkManager.GetType(), DisplayNetworkManagerProperties, networkManager.NetworkManagerExpanded, SetExpanded); + DisplayCallToActionButtons(); + base.OnInspectorGUI(); + } + private static void DrawInstallMultiplayerToolsTip() { const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager."; diff --git a/com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs new file mode 100644 index 0000000000..8ab99436d8 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs @@ -0,0 +1,42 @@ +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D +using Unity.Netcode.Components; +using UnityEditor; + +namespace Unity.Netcode.Editor +{ + [CustomEditor(typeof(NetworkRigidbodyBase), true)] + [CanEditMultipleObjects] + public class NetworkRigidbodyBaseEditor : NetcodeEditorBase + { + private SerializedProperty m_UseRigidBodyForMotion; + private SerializedProperty m_AutoUpdateKinematicState; + private SerializedProperty m_AutoSetKinematicOnDespawn; + + + public override void OnEnable() + { + m_UseRigidBodyForMotion = serializedObject.FindProperty(nameof(NetworkRigidbodyBase.UseRigidBodyForMotion)); + m_AutoUpdateKinematicState = serializedObject.FindProperty(nameof(NetworkRigidbodyBase.AutoUpdateKinematicState)); + m_AutoSetKinematicOnDespawn = serializedObject.FindProperty(nameof(NetworkRigidbodyBase.AutoSetKinematicOnDespawn)); + + base.OnEnable(); + } + + private void DisplayNetworkRigidbodyProperties() + { + EditorGUILayout.PropertyField(m_UseRigidBodyForMotion); + EditorGUILayout.PropertyField(m_AutoUpdateKinematicState); + EditorGUILayout.PropertyField(m_AutoSetKinematicOnDespawn); + } + + /// + public override void OnInspectorGUI() + { + var networkRigidbodyBase = target as NetworkRigidbodyBase; + void SetExpanded(bool expanded) { networkRigidbodyBase.NetworkRigidbodyBaseExpanded = expanded; }; + DrawFoldOutGroup(networkRigidbodyBase.GetType(), DisplayNetworkRigidbodyProperties, networkRigidbodyBase.NetworkRigidbodyBaseExpanded, SetExpanded); + base.OnInspectorGUI(); + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs.meta b/com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs.meta new file mode 100644 index 0000000000..c7fef8a318 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/NetworkRigidbodyBaseEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 06561c57f81a6354f8bb16076f1de3a9 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 4affff1ffb..7d8b26522c 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -8,8 +8,11 @@ namespace Unity.Netcode.Editor /// The for ///
[CustomEditor(typeof(NetworkTransform), true)] - public class NetworkTransformEditor : UnityEditor.Editor + [CanEditMultipleObjects] + public class NetworkTransformEditor : NetcodeEditorBase { + private SerializedProperty m_SwitchTransformSpaceWhenParented; + private SerializedProperty m_TickSyncChildren; private SerializedProperty m_UseUnreliableDeltas; private SerializedProperty m_SyncPositionXProperty; private SerializedProperty m_SyncPositionYProperty; @@ -39,8 +42,10 @@ public class NetworkTransformEditor : UnityEditor.Editor private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale"); /// - public virtual void OnEnable() + public override void OnEnable() { + m_SwitchTransformSpaceWhenParented = serializedObject.FindProperty(nameof(NetworkTransform.SwitchTransformSpaceWhenParented)); + m_TickSyncChildren = serializedObject.FindProperty(nameof(NetworkTransform.TickSyncChildren)); m_UseUnreliableDeltas = serializedObject.FindProperty(nameof(NetworkTransform.UseUnreliableDeltas)); m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX)); m_SyncPositionYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionY)); @@ -61,10 +66,10 @@ public virtual void OnEnable() m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision)); m_SlerpPosition = serializedObject.FindProperty(nameof(NetworkTransform.SlerpPosition)); m_AuthorityMode = serializedObject.FindProperty(nameof(NetworkTransform.AuthorityMode)); + base.OnEnable(); } - /// - public override void OnInspectorGUI() + private void DisplayNetworkTransformProperties() { var networkTransform = target as NetworkTransform; EditorGUILayout.LabelField("Axis to Synchronize", EditorStyles.boldLabel); @@ -141,9 +146,15 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(m_ScaleThresholdProperty); EditorGUILayout.Space(); EditorGUILayout.LabelField("Delivery", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(m_TickSyncChildren); EditorGUILayout.PropertyField(m_UseUnreliableDeltas); EditorGUILayout.Space(); EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(m_SwitchTransformSpaceWhenParented); + if (m_SwitchTransformSpaceWhenParented.boolValue) + { + m_TickSyncChildren.boolValue = true; + } EditorGUILayout.PropertyField(m_InLocalSpaceProperty); if (!networkTransform.HideInterpolateValue) { @@ -163,8 +174,7 @@ public override void OnInspectorGUI() #if COM_UNITY_MODULES_PHYSICS // if rigidbody is present but network rigidbody is not present - var go = ((NetworkTransform)target).gameObject; - if (go.TryGetComponent(out _) && go.TryGetComponent(out _) == false) + if (networkTransform.TryGetComponent(out _) && networkTransform.TryGetComponent(out _) == false) { EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" + "Add a NetworkRigidbody component to improve Rigidbody synchronization.", MessageType.Warning); @@ -172,14 +182,23 @@ public override void OnInspectorGUI() #endif // COM_UNITY_MODULES_PHYSICS #if COM_UNITY_MODULES_PHYSICS2D - if (go.TryGetComponent(out _) && go.TryGetComponent(out _) == false) + if (networkTransform.TryGetComponent(out _) && networkTransform.TryGetComponent(out _) == false) { EditorGUILayout.HelpBox("This GameObject contains a Rigidbody2D but no NetworkRigidbody2D.\n" + "Add a NetworkRigidbody2D component to improve Rigidbody2D synchronization.", MessageType.Warning); } #endif // COM_UNITY_MODULES_PHYSICS2D + } + - serializedObject.ApplyModifiedProperties(); + + /// + public override void OnInspectorGUI() + { + var networkTransform = target as NetworkTransform; + void SetExpanded(bool expanded) { networkTransform.NetworkTransformExpanded = expanded; }; + DrawFoldOutGroup(networkTransform.GetType(), DisplayNetworkTransformProperties, networkTransform.NetworkTransformExpanded, SetExpanded); + base.OnInspectorGUI(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index 10c1d18979..21d3c054bf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -239,19 +239,13 @@ public void AnticipateState(TransformState newState) m_CurrentSmoothTime = 0; } - public override void OnUpdate() + private void ProcessSmoothing() { // If not spawned or this instance has authority, exit early if (!IsSpawned) { return; } - // Do not call the base class implementation... - // AnticipatedNetworkTransform applies its authoritative state immediately rather than waiting for update - // This is because AnticipatedNetworkTransforms may need to reference each other in reanticipating - // and we will want all reanticipation done before anything else wants to reference the transform in - // OnUpdate() - //base.Update(); if (m_CurrentSmoothTime < m_SmoothDuration) { @@ -262,7 +256,7 @@ public override void OnUpdate() m_AnticipatedTransform = new TransformState { Position = Vector3.Lerp(m_SmoothFrom.Position, m_SmoothTo.Position, pct), - Rotation = Quaternion.Slerp(m_SmoothFrom.Rotation, m_SmoothTo.Rotation, pct), + Rotation = Quaternion.Lerp(m_SmoothFrom.Rotation, m_SmoothTo.Rotation, pct), Scale = Vector3.Lerp(m_SmoothFrom.Scale, m_SmoothTo.Scale, pct) }; m_PreviousAnticipatedTransform = m_AnticipatedTransform; @@ -275,6 +269,32 @@ public override void OnUpdate() } } + // TODO: This does not handle OnFixedUpdate + // This requires a complete overhaul in this class to switch between using + // NetworkRigidbody's position and rotation values. + public override void OnUpdate() + { + ProcessSmoothing(); + // Do not call the base class implementation... + // AnticipatedNetworkTransform applies its authoritative state immediately rather than waiting for update + // This is because AnticipatedNetworkTransforms may need to reference each other in reanticipating + // and we will want all reanticipation done before anything else wants to reference the transform in + // OnUpdate() + //base.OnUpdate(); + } + + /// + /// Since authority does not subscribe to updates (OnUpdate or OnFixedUpdate), + /// we have to update every frame to assure authority processes soothing. + /// + private void Update() + { + if (CanCommitToTransform && IsSpawned) + { + ProcessSmoothing(); + } + } + internal class AnticipatedObject : IAnticipationEventReceiver, IAnticipatedObject { public AnticipatedNetworkTransform Transform; @@ -347,14 +367,44 @@ private void ResetAnticipatedState() m_CurrentSmoothTime = 0; } - protected override void OnSynchronize(ref BufferSerializer serializer) + /// + /// (This replaces the first OnSynchronize for NetworkTransforms) + /// This is needed to initialize when fully synchronized since non-authority instances + /// don't apply the initial synchronization (new client synchronization) until after + /// everything has been spawned and synchronized. + /// + protected internal override void InternalOnNetworkSessionSynchronized() { - base.OnSynchronize(ref serializer); - if (!CanCommitToTransform) + var wasSynchronizing = SynchronizeState.IsSynchronizing; + base.InternalOnNetworkSessionSynchronized(); + if (!CanCommitToTransform && wasSynchronizing && !SynchronizeState.IsSynchronizing) + { + m_OutstandingAuthorityChange = true; + ApplyAuthoritativeState(); + ResetAnticipatedState(); + + m_AnticipatedObject = new AnticipatedObject { Transform = this }; + NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); + NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); + } + } + + /// + /// (This replaces the any subsequent OnSynchronize for NetworkTransforms post client synchronization) + /// This occurs on already connected clients when dynamically spawning a NetworkObject for + /// non-authoritative instances. + /// + protected internal override void InternalOnNetworkPostSpawn() + { + base.InternalOnNetworkPostSpawn(); + if (!CanCommitToTransform && NetworkManager.IsConnectedClient && !SynchronizeState.IsSynchronizing) { m_OutstandingAuthorityChange = true; ApplyAuthoritativeState(); ResetAnticipatedState(); + m_AnticipatedObject = new AnticipatedObject { Transform = this }; + NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject); + NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); } } @@ -365,6 +415,13 @@ public override void OnNetworkSpawn() Debug.LogWarning($"This component is not currently supported in distributed authority."); } base.OnNetworkSpawn(); + + // Non-authoritative instances exit early if the synchronization has yet to + // be applied at this point + if (SynchronizeState.IsSynchronizing && !CanCommitToTransform) + { + return; + } m_OutstandingAuthorityChange = true; ApplyAuthoritativeState(); ResetAnticipatedState(); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index ef5ec09d86..e628c7cab2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode public abstract class BufferedLinearInterpolator where T : struct { internal float MaxInterpolationBound = 3.0f; - private struct BufferedItem + protected internal struct BufferedItem { public T Item; public double TimeSent; @@ -31,14 +31,16 @@ public BufferedItem(T item, double timeSent) private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator - private T m_InterpStartValue; - private T m_CurrentInterpValue; - private T m_InterpEndValue; + protected internal T m_InterpStartValue; + protected internal T m_CurrentInterpValue; + protected internal T m_InterpEndValue; private double m_EndTimeConsumed; private double m_StartTimeConsumed; - private readonly List m_Buffer = new List(k_BufferCountLimit); + protected internal readonly List m_Buffer = new List(k_BufferCountLimit); + + // Buffer consumption scenarios // Perfect case consumption @@ -73,6 +75,21 @@ public BufferedItem(T item, double timeSent) private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0; + internal bool EndOfBuffer => m_Buffer.Count == 0; + + internal bool InLocalSpace; + + protected internal virtual void OnConvertTransformSpace(Transform transform, bool inLocalSpace) + { + + } + + internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) + { + OnConvertTransformSpace(transform, inLocalSpace); + InLocalSpace = inLocalSpace; + } + /// /// Resets interpolator to initial state /// @@ -351,6 +368,35 @@ protected override Quaternion Interpolate(Quaternion start, Quaternion end, floa return Quaternion.Lerp(start, end, time); } } + + private Quaternion ConvertToNewTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) + { + if (inLocalSpace) + { + return Quaternion.Inverse(transform.rotation) * rotation; + + } + else + { + return transform.rotation * rotation; + } + } + + protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) + { + for (int i = 0; i < m_Buffer.Count; i++) + { + var entry = m_Buffer[i]; + entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); + m_Buffer[i] = entry; + } + + m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); + m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); + m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); + + base.OnConvertTransformSpace(transform, inLocalSpace); + } } /// @@ -388,5 +434,34 @@ protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) return Vector3.Lerp(start, end, time); } } + + private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) + { + if (inLocalSpace) + { + return transform.InverseTransformPoint(position); + + } + else + { + return transform.TransformPoint(position); + } + } + + protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) + { + for (int i = 0; i < m_Buffer.Count; i++) + { + var entry = m_Buffer[i]; + entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); + m_Buffer[i] = entry; + } + + m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); + m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); + m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); + + base.OnConvertTransformSpace(transform, inLocalSpace); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index a85f7c09bd..7e8808171a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -1,4 +1,4 @@ -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D using System.Runtime.CompilerServices; using UnityEngine; @@ -14,6 +14,12 @@ namespace Unity.Netcode.Components /// public abstract class NetworkRigidbodyBase : NetworkBehaviour { +#if UNITY_EDITOR + [HideInInspector] + [SerializeField] + internal bool NetworkRigidbodyBaseExpanded; +#endif + /// /// When enabled, the associated will use the Rigidbody/Rigidbody2D to apply and synchronize changes in position, rotation, and /// allows for the use of Rigidbody interpolation/extrapolation. @@ -42,8 +48,10 @@ public abstract class NetworkRigidbodyBase : NetworkBehaviour private bool m_IsRigidbody2D => RigidbodyType == RigidbodyTypes.Rigidbody2D; // Used to cache the authority state of this Rigidbody during the last frame private bool m_IsAuthority; - private Rigidbody m_Rigidbody; - private Rigidbody2D m_Rigidbody2D; + + protected internal Rigidbody m_InternalRigidbody { get; private set; } + protected internal Rigidbody2D m_InternalRigidbody2D { get; private set; } + internal NetworkTransform NetworkTransform; private float m_TickFrequency; private float m_TickRate; @@ -87,18 +95,18 @@ protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform network return; } RigidbodyType = rigidbodyType; - m_Rigidbody2D = rigidbody2D; - m_Rigidbody = rigidbody; + m_InternalRigidbody2D = rigidbody2D; + m_InternalRigidbody = rigidbody; NetworkTransform = networkTransform; - if (m_IsRigidbody2D && m_Rigidbody2D == null) + if (m_IsRigidbody2D && m_InternalRigidbody2D == null) { - m_Rigidbody2D = GetComponent(); + m_InternalRigidbody2D = GetComponent(); } - else if (m_Rigidbody == null) + else if (m_InternalRigidbody == null) { - m_Rigidbody = GetComponent(); + m_InternalRigidbody = GetComponent(); } SetOriginalInterpolation(); @@ -178,14 +186,14 @@ public void SetLinearVelocity(Vector3 linearVelocity) if (m_IsRigidbody2D) { #if COM_UNITY_MODULES_PHYSICS2D_LINEAR - m_Rigidbody2D.linearVelocity = linearVelocity; + m_InternalRigidbody2D.linearVelocity = linearVelocity; #else - m_Rigidbody2D.velocity = linearVelocity; + m_InternalRigidbody2D.velocity = linearVelocity; #endif } else { - m_Rigidbody.linearVelocity = linearVelocity; + m_InternalRigidbody.linearVelocity = linearVelocity; } } @@ -202,14 +210,14 @@ public Vector3 GetLinearVelocity() if (m_IsRigidbody2D) { #if COM_UNITY_MODULES_PHYSICS2D_LINEAR - return m_Rigidbody2D.linearVelocity; + return m_InternalRigidbody2D.linearVelocity; #else - return m_Rigidbody2D.velocity; + return m_InternalRigidbody2D.velocity; #endif } else { - return m_Rigidbody.linearVelocity; + return m_InternalRigidbody.linearVelocity; } } @@ -226,11 +234,11 @@ public void SetAngularVelocity(Vector3 angularVelocity) { if (m_IsRigidbody2D) { - m_Rigidbody2D.angularVelocity = angularVelocity.z; + m_InternalRigidbody2D.angularVelocity = angularVelocity.z; } else { - m_Rigidbody.angularVelocity = angularVelocity; + m_InternalRigidbody.angularVelocity = angularVelocity; } } @@ -246,11 +254,11 @@ public Vector3 GetAngularVelocity() { if (m_IsRigidbody2D) { - return Vector3.forward * m_Rigidbody2D.angularVelocity; + return Vector3.forward * m_InternalRigidbody2D.angularVelocity; } else { - return m_Rigidbody.angularVelocity; + return m_InternalRigidbody.angularVelocity; } } @@ -263,11 +271,11 @@ public Vector3 GetPosition() { if (m_IsRigidbody2D) { - return m_Rigidbody2D.position; + return m_InternalRigidbody2D.position; } else { - return m_Rigidbody.position; + return m_InternalRigidbody.position; } } @@ -282,13 +290,13 @@ public Quaternion GetRotation() { var quaternion = Quaternion.identity; var angles = quaternion.eulerAngles; - angles.z = m_Rigidbody2D.rotation; + angles.z = m_InternalRigidbody2D.rotation; quaternion.eulerAngles = angles; return quaternion; } else { - return m_Rigidbody.rotation; + return m_InternalRigidbody.rotation; } } @@ -301,11 +309,11 @@ public void MovePosition(Vector3 position) { if (m_IsRigidbody2D) { - m_Rigidbody2D.MovePosition(position); + m_InternalRigidbody2D.MovePosition(position); } else { - m_Rigidbody.MovePosition(position); + m_InternalRigidbody.MovePosition(position); } } @@ -318,11 +326,11 @@ public void SetPosition(Vector3 position) { if (m_IsRigidbody2D) { - m_Rigidbody2D.position = position; + m_InternalRigidbody2D.position = position; } else { - m_Rigidbody.position = position; + m_InternalRigidbody.position = position; } } @@ -334,13 +342,13 @@ public void ApplyCurrentTransform() { if (m_IsRigidbody2D) { - m_Rigidbody2D.position = transform.position; - m_Rigidbody2D.rotation = transform.eulerAngles.z; + m_InternalRigidbody2D.position = transform.position; + m_InternalRigidbody2D.rotation = transform.eulerAngles.z; } else { - m_Rigidbody.position = transform.position; - m_Rigidbody.rotation = transform.rotation; + m_InternalRigidbody.position = transform.position; + m_InternalRigidbody.rotation = transform.rotation; } } @@ -358,9 +366,9 @@ public void MoveRotation(Quaternion rotation) { var quaternion = Quaternion.identity; var angles = quaternion.eulerAngles; - angles.z = m_Rigidbody2D.rotation; + angles.z = m_InternalRigidbody2D.rotation; quaternion.eulerAngles = angles; - m_Rigidbody2D.MoveRotation(quaternion); + m_InternalRigidbody2D.MoveRotation(quaternion); } else { @@ -375,7 +383,7 @@ public void MoveRotation(Quaternion rotation) { rotation.Normalize(); } - m_Rigidbody.MoveRotation(rotation); + m_InternalRigidbody.MoveRotation(rotation); } } @@ -388,11 +396,11 @@ public void SetRotation(Quaternion rotation) { if (m_IsRigidbody2D) { - m_Rigidbody2D.rotation = rotation.eulerAngles.z; + m_InternalRigidbody2D.rotation = rotation.eulerAngles.z; } else { - m_Rigidbody.rotation = rotation; + m_InternalRigidbody.rotation = rotation; } } @@ -404,7 +412,7 @@ private void SetOriginalInterpolation() { if (m_IsRigidbody2D) { - switch (m_Rigidbody2D.interpolation) + switch (m_InternalRigidbody2D.interpolation) { case RigidbodyInterpolation2D.None: { @@ -425,7 +433,7 @@ private void SetOriginalInterpolation() } else { - switch (m_Rigidbody.interpolation) + switch (m_InternalRigidbody.interpolation) { case RigidbodyInterpolation.None: { @@ -454,16 +462,16 @@ public void WakeIfSleeping() { if (m_IsRigidbody2D) { - if (m_Rigidbody2D.IsSleeping()) + if (m_InternalRigidbody2D.IsSleeping()) { - m_Rigidbody2D.WakeUp(); + m_InternalRigidbody2D.WakeUp(); } } else { - if (m_Rigidbody.IsSleeping()) + if (m_InternalRigidbody.IsSleeping()) { - m_Rigidbody.WakeUp(); + m_InternalRigidbody.WakeUp(); } } } @@ -476,11 +484,11 @@ public void SleepRigidbody() { if (m_IsRigidbody2D) { - m_Rigidbody2D.Sleep(); + m_InternalRigidbody2D.Sleep(); } else { - m_Rigidbody.Sleep(); + m_InternalRigidbody.Sleep(); } } @@ -489,11 +497,11 @@ public bool IsKinematic() { if (m_IsRigidbody2D) { - return m_Rigidbody2D.bodyType == RigidbodyType2D.Kinematic; + return m_InternalRigidbody2D.bodyType == RigidbodyType2D.Kinematic; } else { - return m_Rigidbody.isKinematic; + return m_InternalRigidbody.isKinematic; } } @@ -518,11 +526,11 @@ public void SetIsKinematic(bool isKinematic) { if (m_IsRigidbody2D) { - m_Rigidbody2D.bodyType = isKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic; + m_InternalRigidbody2D.bodyType = isKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic; } else { - m_Rigidbody.isKinematic = isKinematic; + m_InternalRigidbody.isKinematic = isKinematic; } // If we are not spawned, then exit early @@ -539,7 +547,7 @@ public void SetIsKinematic(bool isKinematic) if (IsKinematic()) { // If not already set to interpolate then set the Rigidbody to interpolate - if (m_Rigidbody.interpolation == RigidbodyInterpolation.Extrapolate) + if (m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate) { // Sleep until the next fixed update when switching from extrapolation to interpolation SleepRigidbody(); @@ -568,11 +576,11 @@ private void SetInterpolation(InterpolationTypes interpolationType) { if (m_IsRigidbody2D) { - m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.None; + m_InternalRigidbody2D.interpolation = RigidbodyInterpolation2D.None; } else { - m_Rigidbody.interpolation = RigidbodyInterpolation.None; + m_InternalRigidbody.interpolation = RigidbodyInterpolation.None; } break; } @@ -580,11 +588,11 @@ private void SetInterpolation(InterpolationTypes interpolationType) { if (m_IsRigidbody2D) { - m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate; + m_InternalRigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate; } else { - m_Rigidbody.interpolation = RigidbodyInterpolation.Interpolate; + m_InternalRigidbody.interpolation = RigidbodyInterpolation.Interpolate; } break; } @@ -592,11 +600,11 @@ private void SetInterpolation(InterpolationTypes interpolationType) { if (m_IsRigidbody2D) { - m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Extrapolate; + m_InternalRigidbody2D.interpolation = RigidbodyInterpolation2D.Extrapolate; } else { - m_Rigidbody.interpolation = RigidbodyInterpolation.Extrapolate; + m_InternalRigidbody.interpolation = RigidbodyInterpolation.Extrapolate; } break; } @@ -711,28 +719,28 @@ protected virtual void OnFixedJoint2DCreated() private void ApplyFixedJoint2D(NetworkRigidbodyBase bodyToConnect, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true) { transform.position = position; - m_Rigidbody2D.position = position; - m_OriginalGravitySetting = bodyToConnect.m_Rigidbody.useGravity; + m_InternalRigidbody2D.position = position; + m_OriginalGravitySetting = bodyToConnect.m_InternalRigidbody.useGravity; m_FixedJoint2DUsingGravity = useGravity; if (!useGravity) { - m_OriginalGravityScale = m_Rigidbody2D.gravityScale; - m_Rigidbody2D.gravityScale = 0.0f; + m_OriginalGravityScale = m_InternalRigidbody2D.gravityScale; + m_InternalRigidbody2D.gravityScale = 0.0f; } if (zeroVelocity) { #if COM_UNITY_MODULES_PHYSICS2D_LINEAR - m_Rigidbody2D.linearVelocity = Vector2.zero; + m_InternalRigidbody2D.linearVelocity = Vector2.zero; #else - m_Rigidbody2D.velocity = Vector2.zero; + m_InternalRigidbody2D.velocity = Vector2.zero; #endif - m_Rigidbody2D.angularVelocity = 0.0f; + m_InternalRigidbody2D.angularVelocity = 0.0f; } FixedJoint2D = gameObject.AddComponent(); - FixedJoint2D.connectedBody = bodyToConnect.m_Rigidbody2D; + FixedJoint2D.connectedBody = bodyToConnect.m_InternalRigidbody2D; OnFixedJoint2DCreated(); } @@ -740,16 +748,16 @@ private void ApplyFixedJoint2D(NetworkRigidbodyBase bodyToConnect, Vector3 posit private void ApplyFixedJoint(NetworkRigidbodyBase bodyToConnectTo, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true) { transform.position = position; - m_Rigidbody.position = position; + m_InternalRigidbody.position = position; if (zeroVelocity) { - m_Rigidbody.linearVelocity = Vector3.zero; - m_Rigidbody.angularVelocity = Vector3.zero; + m_InternalRigidbody.linearVelocity = Vector3.zero; + m_InternalRigidbody.angularVelocity = Vector3.zero; } - m_OriginalGravitySetting = m_Rigidbody.useGravity; - m_Rigidbody.useGravity = useGravity; + m_OriginalGravitySetting = m_InternalRigidbody.useGravity; + m_InternalRigidbody.useGravity = useGravity; FixedJoint = gameObject.AddComponent(); - FixedJoint.connectedBody = bodyToConnectTo.m_Rigidbody; + FixedJoint.connectedBody = bodyToConnectTo.m_InternalRigidbody; FixedJoint.connectedMassScale = connectedMassScale; FixedJoint.massScale = massScale; OnFixedJointCreated(); @@ -861,7 +869,7 @@ public void DetachFromFixedJoint() if (FixedJoint != null) { FixedJoint.connectedBody = null; - m_Rigidbody.useGravity = m_OriginalGravitySetting; + m_InternalRigidbody.useGravity = m_OriginalGravitySetting; Destroy(FixedJoint); FixedJoint = null; ResetInterpolation(); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs index 7c93a5deac..a157d26c63 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody.cs @@ -12,6 +12,9 @@ namespace Unity.Netcode.Components [AddComponentMenu("Netcode/Network Rigidbody")] public class NetworkRigidbody : NetworkRigidbodyBase { + + public Rigidbody Rigidbody => m_InternalRigidbody; + protected virtual void Awake() { Initialize(RigidbodyTypes.Rigidbody); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs index a178660df5..f7c9e14d1e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidbody2D.cs @@ -12,6 +12,7 @@ namespace Unity.Netcode.Components [AddComponentMenu("Netcode/Network Rigidbody 2D")] public class NetworkRigidbody2D : NetworkRigidbodyBase { + public Rigidbody2D Rigidbody2D => m_InternalRigidbody2D; protected virtual void Awake() { Initialize(RigidbodyTypes.Rigidbody2D); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 0cf2b41631..13fb21a5ce 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -20,6 +20,10 @@ public class NetworkTransform : NetworkBehaviour #if UNITY_EDITOR internal virtual bool HideInterpolateValue => false; + + [HideInInspector] + [SerializeField] + internal bool NetworkTransformExpanded; #endif #region NETWORK TRANSFORM STATE @@ -941,6 +945,19 @@ public enum AuthorityModes #endif public AuthorityModes AuthorityMode; + + /// + /// When enabled, any parented s (children) of this will be forced to synchronize their transform when this instance sends a state update.
+ /// This can help to reduce out of sync updates that can lead to slight jitter between a parent and its child/children. + ///
+ /// + /// - If this is set on a child and the parent does not have this set then the child will not be tick synchronized with its parent.
+ /// - If the parent instance does not send any state updates, the children will still send state updates when exceeding axis delta threshold.
+ /// - This does not need to be set on children to be applied. + ///
+ [Tooltip("When enabled, any parented children of this instance will send a state update when this instance sends a state update. If this instance doesn't send a state update, the children will still send state updates when reaching their axis specified threshold delta. Children do not have to have this setting enabled.")] + public bool TickSyncChildren = false; + /// /// The default position change threshold value. /// Any changes above this threshold will be replicated. @@ -1175,6 +1192,22 @@ private bool SynchronizeScale [Tooltip("Sets whether this transform should sync in local space or in world space")] public bool InLocalSpace = false; + /// + /// When enabled, the NetworkTransform will automatically handle transitioning into the respective transform space when its parent changes.
+ /// When parented: Automatically transitions into local space and coverts any existing pending interpolated states to local space on non-authority instances.
+ /// When deparented: Automatically transitions into world space and converts any existing pending interpolated states to world space on non-authority instances.
+ /// Set on the root instance (nested components should be pre-set in-editor to local space.
+ ///
+ /// + /// Only works with components that are not paired with a or component that is configured to use the rigid body for motion.
+ /// will automatically be set when this is enabled. + /// Does not auto-synchronize clients if changed on the authority instance during runtime (i.e. apply this setting in-editor). + ///
+ public bool SwitchTransformSpaceWhenParented = false; + + protected bool PositionInLocalSpace => (!SwitchTransformSpaceWhenParented && InLocalSpace) || (m_PositionInterpolator != null && m_PositionInterpolator.InLocalSpace && SwitchTransformSpaceWhenParented); + protected bool RotationInLocalSpace => (!SwitchTransformSpaceWhenParented && InLocalSpace) || (m_RotationInterpolator != null && m_RotationInterpolator.InLocalSpace && SwitchTransformSpaceWhenParented); + /// /// When enabled (default) interpolation is applied. /// When disabled interpolation is disabled. @@ -1248,7 +1281,7 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) else { // Otherwise, just get the current position - return m_CurrentPosition; + return m_InternalCurrentPosition; } } } @@ -1281,7 +1314,7 @@ public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) } else { - return m_CurrentRotation; + return m_InternalCurrentRotation; } } @@ -1312,7 +1345,7 @@ public Vector3 GetScale(bool getCurrentState = false) } else { - return m_CurrentScale; + return m_InternalCurrentScale; } } @@ -1344,15 +1377,14 @@ internal NetworkTransformState LocalAuthoritativeNetworkState // Non-Authoritative's current position, scale, and rotation that is used to assure the non-authoritative side cannot make adjustments to // the portions of the transform being synchronized. - private Vector3 m_CurrentPosition; + private Vector3 m_InternalCurrentPosition; private Vector3 m_TargetPosition; - private Vector3 m_CurrentScale; + private Vector3 m_InternalCurrentScale; private Vector3 m_TargetScale; - private Quaternion m_CurrentRotation; + private Quaternion m_InternalCurrentRotation; private Vector3 m_TargetRotation; - // DANGO-EXP TODO: ADD Rigidbody2D -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D private bool m_UseRigidbodyForMotion; private NetworkRigidbodyBase m_NetworkRigidbodyInternal; @@ -1436,6 +1468,7 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) #endregion #region ONSYNCHRONIZE + /// /// This is invoked when a new client joins (server and client sides) /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState) @@ -1450,7 +1483,7 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) protected override void OnSynchronize(ref BufferSerializer serializer) { var targetClientId = m_TargetIdBeingSynchronized; - var synchronizationState = new NetworkTransformState() + SynchronizeState = new NetworkTransformState() { HalfEulerRotation = new HalfVector3(), HalfVectorRotation = new HalfVector4(), @@ -1469,34 +1502,39 @@ protected override void OnSynchronize(ref BufferSerializer serializer) writer.WriteValueSafe(k_NetworkTransformStateMagic); } - synchronizationState.IsTeleportingNextFrame = true; + SynchronizeState.IsTeleportingNextFrame = true; var transformToCommit = transform; // If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for // for the non-authority side to be able to properly synchronize delta position updates. - CheckForStateChange(ref synchronizationState, ref transformToCommit, true, targetClientId); - synchronizationState.NetworkSerialize(serializer); - SynchronizeState = synchronizationState; + CheckForStateChange(ref SynchronizeState, ref transformToCommit, true, targetClientId); + SynchronizeState.NetworkSerialize(serializer); } else { - synchronizationState.NetworkSerialize(serializer); - // Set the transform's synchronization modes - InLocalSpace = synchronizationState.InLocalSpace; - Interpolate = synchronizationState.UseInterpolation; - UseQuaternionSynchronization = synchronizationState.QuaternionSync; - UseHalfFloatPrecision = synchronizationState.UseHalfFloatPrecision; - UseQuaternionCompression = synchronizationState.QuaternionCompression; - SlerpPosition = synchronizationState.UsePositionSlerp; - UpdatePositionSlerp(); - - // Teleport/Fully Initialize based on the state - ApplyTeleportingState(synchronizationState); - SynchronizeState = synchronizationState; - m_LocalAuthoritativeNetworkState = synchronizationState; - m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; - m_LocalAuthoritativeNetworkState.IsSynchronizing = false; + SynchronizeState.NetworkSerialize(serializer); } } + + /// + /// We now apply synchronization after everything has spawned + /// + private void ApplySynchronization() + { + // Set the transform's synchronization modes + InLocalSpace = SynchronizeState.InLocalSpace; + Interpolate = SynchronizeState.UseInterpolation; + UseQuaternionSynchronization = SynchronizeState.QuaternionSync; + UseHalfFloatPrecision = SynchronizeState.UseHalfFloatPrecision; + UseQuaternionCompression = SynchronizeState.QuaternionCompression; + SlerpPosition = SynchronizeState.UsePositionSlerp; + UpdatePositionSlerp(); + // Teleport/Fully Initialize based on the state + ApplyTeleportingState(SynchronizeState); + m_LocalAuthoritativeNetworkState = SynchronizeState; + m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; + m_LocalAuthoritativeNetworkState.IsSynchronizing = false; + SynchronizeState.IsSynchronizing = false; + } #endregion #region AUTHORITY STATE UPDATE @@ -1577,7 +1615,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz NetworkLog.LogError($"[{name}] is trying to commit the transform without authority!"); return; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D // TODO: Make this an authority flag // For now, just synchronize with the NetworkRigidbodyBase UseRigidBodyForMotion if (m_NetworkRigidbodyInternal != null) @@ -1587,7 +1625,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz #endif // If the transform has deltas (returns dirty) or if an explicitly set state is pending - if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize)) + if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize, forceState: settingState)) { // If the state was explicitly set, then update the network tick to match the locally calculate tick if (m_LocalAuthoritativeNetworkState.ExplicitSet) @@ -1629,7 +1667,7 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz m_DeltaSynch = true; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D // We handle updating attached bodies when the "parent" body has a state update in order to keep their delta state updates tick synchronized. if (m_UseRigidbodyForMotion && m_NetworkRigidbodyInternal.NetworkRigidbodyConnections.Count > 0) { @@ -1639,6 +1677,36 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz } } #endif + // When enabled, any children will get tick synchronized with state updates + if (TickSyncChildren) + { + // Synchronize any nested NetworkTransforms with the parent's + foreach (var childNetworkTransform in NetworkObject.NetworkTransforms) + { + // Don't update the same instance + if (childNetworkTransform == this) + { + continue; + } + if (childNetworkTransform.CanCommitToTransform) + { + childNetworkTransform.OnNetworkTick(true); + } + } + + // Synchronize any parented children with the parent's motion + foreach (var child in m_ParentedChildren) + { + // Synchronize any nested NetworkTransforms of the child with the parent's + foreach (var childNetworkTransform in child.NetworkTransforms) + { + if (childNetworkTransform.CanCommitToTransform) + { + childNetworkTransform.OnNetworkTick(true); + } + } + } + } } } @@ -1683,7 +1751,7 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat /// Applies the transform to the specified. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool CheckForStateChange(ref NetworkTransformState networkState, ref Transform transformToUse, bool isSynchronization = false, ulong targetClientId = 0) + private bool CheckForStateChange(ref NetworkTransformState networkState, ref Transform transformToUse, bool isSynchronization = false, ulong targetClientId = 0, bool forceState = false) { // As long as we are not doing our first synchronization and we are sending unreliable deltas, each // NetworkTransform will stagger its full transfom synchronization over a 1 second period based on the @@ -1715,7 +1783,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var isRotationDirty = isTeleportingAndNotSynchronizing ? networkState.HasRotAngleChange : false; var isScaleDirty = isTeleportingAndNotSynchronizing ? networkState.HasScaleChange : false; -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; @@ -1739,17 +1807,18 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // All of the checks below, up to the delta position checking portion, are to determine if the // authority changed a property during runtime that requires a full synchronizing. -#if COM_UNITY_MODULES_PHYSICS - if (InLocalSpace != networkState.InLocalSpace && !m_UseRigidbodyForMotion) +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + if ((InLocalSpace != networkState.InLocalSpace || isSynchronization) && !m_UseRigidbodyForMotion) #else if (InLocalSpace != networkState.InLocalSpace) #endif { - networkState.InLocalSpace = InLocalSpace; + networkState.InLocalSpace = SwitchTransformSpaceWhenParented ? transform.parent != null : InLocalSpace; isDirty = true; - networkState.IsTeleportingNextFrame = true; + networkState.IsTeleportingNextFrame = !SwitchTransformSpaceWhenParented; + forceState = SwitchTransformSpaceWhenParented; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D else if (InLocalSpace && m_UseRigidbodyForMotion) { // TODO: Provide more options than just FixedJoint @@ -1788,28 +1857,6 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra } networkState.IsParented = hasParentNetworkObject; - - // When synchronizing with a parent, world position stays impacts position whether - // the NetworkTransform is using world or local space synchronization. - // WorldPositionStays: (always use world space) - // !WorldPositionStays: (always use local space) - // Exception: If it is an in-scene placed NetworkObject and it is parented under a GameObject - // then always use local space unless AutoObjectParentSync is disabled and the NetworkTransform - // is synchronizing in world space. - if (isSynchronization && networkState.IsParented) - { - var parentedUnderGameObject = NetworkObject.transform.parent != null && !parentNetworkObject && NetworkObject.IsSceneObject.Value; - if (NetworkObject.WorldPositionStays() && (!parentedUnderGameObject || (parentedUnderGameObject && !NetworkObject.AutoObjectParentSync && !InLocalSpace))) - { - position = transformToUse.position; - networkState.InLocalSpace = false; - } - else - { - position = transformToUse.localPosition; - networkState.InLocalSpace = true; - } - } } if (Interpolate != networkState.UseInterpolation) @@ -1858,21 +1905,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // Begin delta checks against last sent state update if (!UseHalfFloatPrecision) { - if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionX = position.x; networkState.HasPositionX = true; isPositionDirty = true; } - if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionY = position.y; networkState.HasPositionY = true; isPositionDirty = true; } - if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.PositionZ = position.z; networkState.HasPositionZ = true; @@ -1882,7 +1929,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra else if (SynchronizePosition) { // If we are teleporting then we can skip the delta threshold check - isPositionDirty = networkState.IsTeleportingNextFrame || isAxisSync; + isPositionDirty = networkState.IsTeleportingNextFrame || isAxisSync || forceState; if (m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick) { isPositionDirty = true; @@ -1988,21 +2035,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (!UseQuaternionSynchronization) { - if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleX = rotAngles.x; networkState.HasRotAngleX = true; isRotationDirty = true; } - if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleY = rotAngles.y; networkState.HasRotAngleY = true; isRotationDirty = true; } - if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.RotAngleZ = rotAngles.z; networkState.HasRotAngleZ = true; @@ -2012,7 +2059,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra else if (SynchronizeRotation) { // If we are teleporting then we can skip the delta threshold check - isRotationDirty = networkState.IsTeleportingNextFrame || isAxisSync; + isRotationDirty = networkState.IsTeleportingNextFrame || isAxisSync || forceState; // For quaternion synchronization, if one angle is dirty we send a full update if (!isRotationDirty) { @@ -2051,21 +2098,21 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra { if (!UseHalfFloatPrecision) { - if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleX = scale.x; networkState.HasScaleX = true; isScaleDirty = true; } - if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleY = scale.y; networkState.HasScaleY = true; isScaleDirty = true; } - if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync)) + if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState)) { networkState.ScaleZ = scale.z; networkState.HasScaleZ = true; @@ -2077,7 +2124,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var previousScale = networkState.Scale; for (int i = 0; i < 3; i++) { - if (Mathf.Abs(scale[i] - previousScale[i]) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync) + if (Mathf.Abs(scale[i] - previousScale[i]) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync || forceState) { isScaleDirty = true; networkState.Scale[i] = scale[i]; @@ -2122,7 +2169,6 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra return isDirty; } - /// /// Authority subscribes to network tick events and will invoke /// each network tick. @@ -2144,7 +2190,13 @@ private void OnNetworkTick(bool isCalledFromParent = false) return; } -#if COM_UNITY_MODULES_PHYSICS + // If we are nested and have already sent a state update this tick, then exit early (otherwise check for any changes in state) + if (IsNested && m_LocalAuthoritativeNetworkState.NetworkTick == m_CachedNetworkManager.ServerTime.Tick) + { + return; + } + +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D // Let the parent handle the updating of this to keep the two synchronized if (!isCalledFromParent && m_UseRigidbodyForMotion && m_NetworkRigidbodyInternal.ParentBody != null && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) { @@ -2154,15 +2206,15 @@ private void OnNetworkTick(bool isCalledFromParent = false) // Update any changes to the transform var transformSource = transform; - OnUpdateAuthoritativeState(ref transformSource); -#if COM_UNITY_MODULES_PHYSICS - m_CurrentPosition = m_TargetPosition = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); + OnUpdateAuthoritativeState(ref transformSource, isCalledFromParent); +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + m_InternalCurrentPosition = m_TargetPosition = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); #else - m_CurrentPosition = GetSpaceRelativePosition(); + m_InternalCurrentPosition = GetSpaceRelativePosition(); m_TargetPosition = GetSpaceRelativePosition(); #endif } - else // If we are no longer authority, unsubscribe to the tick event + else // If we are no longer authority, unsubscribe to the tick event { DeregisterForTickUpdate(this); } @@ -2199,7 +2251,7 @@ protected virtual void OnTransformUpdated() /// protected internal void ApplyAuthoritativeState() { -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D // TODO: Make this an authority flag // For now, just synchronize with the NetworkRigidbodyBase UseRigidBodyForMotion if (m_NetworkRigidbodyInternal != null) @@ -2208,14 +2260,14 @@ protected internal void ApplyAuthoritativeState() } #endif var networkState = m_LocalAuthoritativeNetworkState; - // The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated + // The m_InternalCurrentPosition, m_InternalCurrentRotation, and m_InternalCurrentScale values are continually updated // at the end of this method and assure that when not interpolating the non-authoritative side // cannot make adjustments to any portions the transform not being synchronized. - var adjustedPosition = m_CurrentPosition; - var adjustedRotation = m_CurrentRotation; + var adjustedPosition = m_InternalCurrentPosition; + var adjustedRotation = m_InternalCurrentRotation; var adjustedRotAngles = adjustedRotation.eulerAngles; - var adjustedScale = m_CurrentScale; + var adjustedScale = m_InternalCurrentScale; // Non-Authority Preservers the authority's transform state update modes InLocalSpace = networkState.InLocalSpace; @@ -2234,6 +2286,7 @@ protected internal void ApplyAuthoritativeState() // NOTE ABOUT INTERPOLATING AND THE CODE BELOW: // We always apply the interpolated state for any axis we are synchronizing even when the state has no deltas // to assure we fully interpolate to our target even after we stop extrapolating 1 tick later. + if (Interpolate) { if (SynchronizePosition) @@ -2337,28 +2390,42 @@ protected internal void ApplyAuthoritativeState() // Update our current position if it changed or we are interpolating if (networkState.HasPositionChange || Interpolate) { - m_CurrentPosition = adjustedPosition; + m_InternalCurrentPosition = adjustedPosition; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if (m_UseRigidbodyForMotion) { - m_NetworkRigidbodyInternal.MovePosition(m_CurrentPosition); + m_NetworkRigidbodyInternal.MovePosition(m_InternalCurrentPosition); if (LogMotion) { - Debug.Log($"[Client-{m_CachedNetworkManager.LocalClientId}][Interpolate: {networkState.UseInterpolation}][TransPos: {transform.position}][RBPos: {m_NetworkRigidbodyInternal.GetPosition()}][CurrentPos: {m_CurrentPosition}"); + Debug.Log($"[Client-{m_CachedNetworkManager.LocalClientId}][Interpolate: {networkState.UseInterpolation}][TransPos: {transform.position}][RBPos: {m_NetworkRigidbodyInternal.GetPosition()}][CurrentPos: {m_InternalCurrentPosition}"); } } else #endif { - if (InLocalSpace) + if (PositionInLocalSpace) { - transform.localPosition = m_CurrentPosition; + // This handles the edge case of transitioning from local to world space where applying a local + // space value to a non-parented transform will be applied in world space. Since parenting is not + // tick synchronized, there can be one or two ticks between a state update with the InLocalSpace + // state update which can cause the body to seemingly "teleport" when it is just applying a local + // space value relative to world space 0,0,0. + if (SwitchTransformSpaceWhenParented && m_IsFirstNetworkTransform && Interpolate && m_PreviousNetworkObjectParent != null + && transform.parent == null) + { + m_InternalCurrentPosition = m_PreviousNetworkObjectParent.transform.TransformPoint(m_InternalCurrentPosition); + transform.position = m_InternalCurrentPosition; + } + else + { + transform.localPosition = m_InternalCurrentPosition; + } } else { - transform.position = m_CurrentPosition; + transform.position = m_InternalCurrentPosition; } } } @@ -2369,24 +2436,37 @@ protected internal void ApplyAuthoritativeState() // Update our current rotation if it changed or we are interpolating if (networkState.HasRotAngleChange || Interpolate) { - m_CurrentRotation = adjustedRotation; + m_InternalCurrentRotation = adjustedRotation; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if (m_UseRigidbodyForMotion) { - m_NetworkRigidbodyInternal.MoveRotation(m_CurrentRotation); + m_NetworkRigidbodyInternal.MoveRotation(m_InternalCurrentRotation); } else #endif { - if (InLocalSpace) + if (RotationInLocalSpace) { - transform.localRotation = m_CurrentRotation; + // This handles the edge case of transitioning from local to world space where applying a local + // space value to a non-parented transform will be applied in world space. Since parenting is not + // tick synchronized, there can be one or two ticks between a state update with the InLocalSpace + // state update which can cause the body to rotate world space relative and cause a slight rotation + // of the body in-between this transition period. + if (SwitchTransformSpaceWhenParented && m_IsFirstNetworkTransform && Interpolate && m_PreviousNetworkObjectParent != null && transform.parent == null) + { + m_InternalCurrentRotation = m_PreviousNetworkObjectParent.transform.rotation * m_InternalCurrentRotation; + transform.rotation = m_InternalCurrentRotation; + } + else + { + transform.localRotation = m_InternalCurrentRotation; + } } else { - transform.rotation = m_CurrentRotation; + transform.rotation = m_InternalCurrentRotation; } } } @@ -2397,9 +2477,9 @@ protected internal void ApplyAuthoritativeState() // Update our current scale if it changed or we are interpolating if (networkState.HasScaleChange || Interpolate) { - m_CurrentScale = adjustedScale; + m_InternalCurrentScale = adjustedScale; } - transform.localScale = m_CurrentScale; + transform.localScale = m_InternalCurrentScale; } OnTransformUpdated(); } @@ -2481,7 +2561,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) } } - m_CurrentPosition = currentPosition; + m_InternalCurrentPosition = currentPosition; m_TargetPosition = currentPosition; // Apply the position @@ -2494,7 +2574,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.position = currentPosition; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if (m_UseRigidbodyForMotion) { m_NetworkRigidbodyInternal.SetPosition(transform.position); @@ -2510,17 +2590,6 @@ private void ApplyTeleportingState(NetworkTransformState newState) if (newState.HasScaleChange) { bool shouldUseLossy = false; - if (newState.IsParented) - { - if (transform.parent == null) - { - shouldUseLossy = NetworkObject.WorldPositionStays(); - } - else - { - shouldUseLossy = !NetworkObject.WorldPositionStays(); - } - } if (UseHalfFloatPrecision) { @@ -2545,7 +2614,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) } } - m_CurrentScale = currentScale; + m_InternalCurrentScale = currentScale; m_TargetScale = currentScale; // Apply the adjusted scale @@ -2583,7 +2652,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) currentRotation.eulerAngles = currentEulerAngles; } - m_CurrentRotation = currentRotation; + m_InternalCurrentRotation = currentRotation; m_TargetRotation = currentRotation.eulerAngles; if (InLocalSpace) @@ -2595,7 +2664,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) transform.rotation = currentRotation; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if (m_UseRigidbodyForMotion) { m_NetworkRigidbodyInternal.SetRotation(transform.rotation); @@ -2675,6 +2744,8 @@ private void ApplyUpdatedState(NetworkTransformState newState) return; } + AdjustForChangeInTransformSpace(); + // Apply axial changes from the new state // Either apply the delta position target position or the current state's delta position // depending upon whether UsePositionDeltaCompression is enabled @@ -2682,20 +2753,21 @@ private void ApplyUpdatedState(NetworkTransformState newState) { if (!m_LocalAuthoritativeNetworkState.UseHalfFloatPrecision) { + var position = m_LocalAuthoritativeNetworkState.GetPosition(); var newTargetPosition = m_TargetPosition; if (m_LocalAuthoritativeNetworkState.HasPositionX) { - newTargetPosition.x = m_LocalAuthoritativeNetworkState.PositionX; + newTargetPosition.x = position.x; } if (m_LocalAuthoritativeNetworkState.HasPositionY) { - newTargetPosition.y = m_LocalAuthoritativeNetworkState.PositionY; + newTargetPosition.y = position.y; } if (m_LocalAuthoritativeNetworkState.HasPositionZ) { - newTargetPosition.z = m_LocalAuthoritativeNetworkState.PositionZ; + newTargetPosition.z = position.z; } m_TargetPosition = newTargetPosition; } @@ -2834,6 +2906,37 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf // Apply the new state ApplyUpdatedState(newState); + // Tick synchronize any parented child NetworkObject(s) NetworkTransform(s) + if (TickSyncChildren && m_IsFirstNetworkTransform) + { + // Synchronize any nested NetworkTransforms with the parent's + foreach (var childNetworkTransform in NetworkObject.NetworkTransforms) + { + // Don't update the same instance + if (childNetworkTransform == this) + { + continue; + } + if (childNetworkTransform.CanCommitToTransform) + { + childNetworkTransform.OnNetworkTick(true); + } + } + + // Synchronize any parented children with the parent's motion + foreach (var child in m_ParentedChildren) + { + // Synchronize any nested NetworkTransforms of the child with the parent's + foreach (var childNetworkTransform in child.NetworkTransforms) + { + if (childNetworkTransform.CanCommitToTransform) + { + childNetworkTransform.OnNetworkTick(true); + } + } + } + } + // Provide notifications when the state has been updated // We use the m_LocalAuthoritativeNetworkState because newState has been applied and adjustments could have // been made (i.e. half float precision position values will have been updated) @@ -2902,7 +3005,7 @@ private void AxisChangedDeltaPositionCheck() /// Called by authority to check for deltas and update non-authoritative instances /// if any are found. /// - internal void OnUpdateAuthoritativeState(ref Transform transformSource) + internal void OnUpdateAuthoritativeState(ref Transform transformSource, bool settingState = false) { // If our replicated state is not dirty and our local authority state is dirty, clear it. if (!m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.IsDirty && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) @@ -2922,11 +3025,55 @@ internal void OnUpdateAuthoritativeState(ref Transform transformSource) AxisChangedDeltaPositionCheck(); - TryCommitTransform(ref transformSource); + TryCommitTransform(ref transformSource, settingState: settingState); } #endregion #region SPAWN, DESPAWN, AND INITIALIZATION + + private void NonAuthorityFinalizeSynchronization() + { + // For all child NetworkTransforms nested under the same NetworkObject, + // we apply the initial synchronization based on their parented/ordered + // heirarchy. + if (SynchronizeState.IsSynchronizing && m_IsFirstNetworkTransform) + { + foreach (var child in NetworkObject.NetworkTransforms) + { + child.ApplySynchronization(); + + // For all nested (under the root/same NetworkObject) child NetworkTransforms, we need to run through + // initialization once more to assure any values applied or stored are relative to the Root's transform. + child.InternalInitialization(); + } + } + } + + /// + /// Handle applying the synchronization state once everything has spawned. + /// The first NetowrkTransform handles invoking this on any other nested NetworkTransform. + /// + protected internal override void InternalOnNetworkSessionSynchronized() + { + NonAuthorityFinalizeSynchronization(); + + base.InternalOnNetworkSessionSynchronized(); + } + + /// + /// For dynamically spawned NetworkObjects, when the non-authority instance's client is already connected and + /// the SynchronizeState is still pending synchronization then we want to finalize the synchornization at this time. + /// + protected internal override void InternalOnNetworkPostSpawn() + { + if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) + { + NonAuthorityFinalizeSynchronization(); + } + + base.InternalOnNetworkPostSpawn(); + } + /// /// Create interpolators when first instantiated to avoid memory allocations if the /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) @@ -2942,6 +3089,7 @@ protected virtual void Awake() /// public override void OnNetworkSpawn() { + m_ParentedChildren.Clear(); m_CachedNetworkManager = NetworkManager; Initialize(); @@ -2954,8 +3102,8 @@ public override void OnNetworkSpawn() private void CleanUpOnDestroyOrDespawn() { - -#if COM_UNITY_MODULES_PHYSICS + m_ParentedChildren.Clear(); +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var forUpdate = !m_UseRigidbodyForMotion; #else var forUpdate = true; @@ -2964,6 +3112,7 @@ private void CleanUpOnDestroyOrDespawn() { NetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); } + DeregisterForTickUpdate(this); CanCommitToTransform = false; } @@ -3008,13 +3157,15 @@ protected virtual void OnInitialize(ref NetworkVariable r private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); #else var position = GetSpaceRelativePosition(); var rotation = GetSpaceRelativeRotation(); #endif + m_PositionInterpolator.InLocalSpace = InLocalSpace; + m_RotationInterpolator.InLocalSpace = InLocalSpace; UpdatePositionInterpolator(position, serverTime, true); UpdatePositionSlerp(); @@ -3034,12 +3185,26 @@ private void InternalInitialization(bool isOwnershipChange = false) return; } m_CachedNetworkObject = NetworkObject; + + // Determine if this is the first NetworkTransform in the associated NetworkObject's list + m_IsFirstNetworkTransform = NetworkObject.NetworkTransforms[0] == this; + + if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) { AuthorityMode = AuthorityModes.Owner; } CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner; + if (SwitchTransformSpaceWhenParented) + { + if (CanCommitToTransform) + { + InLocalSpace = transform.parent != null; + } + // Always apply this if SwitchTransformSpaceWhenParented is set. + TickSyncChildren = true; + } var currentPosition = GetSpaceRelativePosition(); var currentRotation = GetSpaceRelativeRotation(); @@ -3050,7 +3215,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_NetworkTransformTickRegistration = s_NetworkTickRegistration[m_CachedNetworkManager]; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D // Depending upon order of operations, we invoke this in order to assure that proper settings are applied. if (m_NetworkRigidbodyInternal) { @@ -3080,7 +3245,7 @@ private void InternalInitialization(bool isOwnershipChange = false) SetState(teleportDisabled: false); } - m_CurrentPosition = currentPosition; + m_InternalCurrentPosition = currentPosition; m_TargetPosition = currentPosition; RegisterForTickUpdate(this); @@ -3097,11 +3262,11 @@ private void InternalInitialization(bool isOwnershipChange = false) // Remove this instance from the tick update DeregisterForTickUpdate(this); ResetInterpolatedStateToCurrentAuthoritativeState(); - m_CurrentPosition = currentPosition; + m_InternalCurrentPosition = currentPosition; m_TargetPosition = currentPosition; - m_CurrentScale = transform.localScale; + m_InternalCurrentScale = transform.localScale; m_TargetScale = transform.localScale; - m_CurrentRotation = currentRotation; + m_InternalCurrentRotation = currentRotation; m_TargetRotation = currentRotation.eulerAngles; } OnInitialize(ref m_LocalAuthoritativeNetworkState); @@ -3117,6 +3282,51 @@ protected void Initialize() #endregion #region PARENTING AND OWNERSHIP + // This might seem aweful, but when transitioning between two parents in local space we need to + // catch the moment the transition happens and only apply the special case parenting from one parent + // to another parent once. Keeping track of the "previous previous" allows us to detect the + // back and fourth scenario: + // - No parent (world space) + // - Parent under NetworkObjectA (world to local) + // - Parent under NetworkObjectB (local to local) (catch with "previous previous") + // - Parent under NetworkObjectA (local to local) (catch with "previous previous") + // - Parent under NetworkObjectB (local to local) (catch with "previous previous") + private NetworkObject m_PreviousCurrentParent; + private NetworkObject m_PreviousPreviousParent; + private void AdjustForChangeInTransformSpace() + { + if (SwitchTransformSpaceWhenParented && m_IsFirstNetworkTransform && (m_PositionInterpolator.InLocalSpace != InLocalSpace || + m_RotationInterpolator.InLocalSpace != InLocalSpace || + (InLocalSpace && m_CurrentNetworkObjectParent && m_PreviousNetworkObjectParent && m_PreviousCurrentParent != m_CurrentNetworkObjectParent && m_PreviousPreviousParent != m_PreviousNetworkObjectParent))) + { + var parent = m_CurrentNetworkObjectParent ? m_CurrentNetworkObjectParent : m_PreviousNetworkObjectParent; + if (parent) + { + // In the event it is a NetworkObject to NetworkObject parenting transfer, we will need to migrate our interpolators + // and our current position and rotation to world space relative to the previous parent before converting them to local + // space relative to the new parent + if (InLocalSpace && m_CurrentNetworkObjectParent && m_PreviousNetworkObjectParent) + { + m_PreviousCurrentParent = m_CurrentNetworkObjectParent; + m_PreviousPreviousParent = m_PreviousNetworkObjectParent; + // Convert our current postion and rotation to world space based on the previous parent's transform + m_InternalCurrentPosition = m_PreviousNetworkObjectParent.transform.TransformPoint(m_InternalCurrentPosition); + m_InternalCurrentRotation = m_PreviousNetworkObjectParent.transform.rotation * m_InternalCurrentRotation; + // Convert our current postion and rotation to local space based on the current parent's transform + m_InternalCurrentPosition = m_CurrentNetworkObjectParent.transform.InverseTransformPoint(m_InternalCurrentPosition); + m_InternalCurrentRotation = Quaternion.Inverse(m_CurrentNetworkObjectParent.transform.rotation) * m_InternalCurrentRotation; + // Convert both interpolators to world space based on the previous parent's transform + m_PositionInterpolator.ConvertTransformSpace(m_PreviousNetworkObjectParent.transform, false); + m_RotationInterpolator.ConvertTransformSpace(m_PreviousNetworkObjectParent.transform, false); + // Next, fall into normal transform space conversion of both interpolators to local space based on the current parent's transform + } + + m_PositionInterpolator.ConvertTransformSpace(parent.transform, InLocalSpace); + m_RotationInterpolator.ConvertTransformSpace(parent.transform, InLocalSpace); + } + } + } + /// public override void OnLostOwnership() { @@ -3139,45 +3349,115 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) base.OnOwnershipChanged(previous, current); } + internal bool IsNested; + private List m_ParentedChildren = new List(); + + private bool m_IsFirstNetworkTransform; + private NetworkObject m_CurrentNetworkObjectParent = null; + private NetworkObject m_PreviousNetworkObjectParent = null; + + internal void ChildRegistration(NetworkObject child, bool isAdding) + { + if (isAdding) + { + m_ParentedChildren.Add(child); + } + else + { + m_ParentedChildren.Remove(child); + } + } + /// /// - /// When a parent changes, non-authoritative instances should: - /// - Apply the resultant position, rotation, and scale from the parenting action. - /// - Clear interpolators (even if not enabled on this frame) - /// - Reset the interpolators to the position, rotation, and scale resultant values. - /// This prevents interpolation visual anomalies and issues during initial synchronization + /// When not using a NetworkRigidbody and using an owner authoritative motion model, you can
+ /// improve parenting transitions into and out of world and local space by:
+ /// - Disabling
+ /// - Enabling
+ /// - Enabling
+ /// -- Note: This handles changing from world space to local space for you.
+ /// When these settings are applied, transitioning from:
+ /// - World space to local space (root-null parent/null to parent) + /// - Local space back to world space ( parent to root-null parent) + /// - Local space to local space ( parent to parent) + /// Will all smoothly transition while interpolation is enabled. + /// (Does not work if using a or for motion) + /// + /// When a parent changes, non-authoritative instances should:
+ /// - Apply the resultant position, rotation, and scale from the parenting action.
+ /// - Clear interpolators (even if not enabled on this frame)
+ /// - Reset the interpolators to the position, rotation, and scale resultant values.
+ /// This prevents interpolation visual anomalies and issues during initial synchronization
///
public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { - // Only if we are not authority - if (!CanCommitToTransform) + base.OnNetworkObjectParentChanged(parentNetworkObject); + } + + + internal override void InternalOnNetworkObjectParentChanged(NetworkObject parentNetworkObject) + { + // The root NetworkTransform handles tracking any NetworkObject parenting since nested NetworkTransforms (of the same NetworkObject) + // will never (or rather should never) change their world space once spawned. +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + // Handling automatic transform space switching can only be applied to NetworkTransforms that don't use the Rigidbody for motion + if (!m_UseRigidbodyForMotion && SwitchTransformSpaceWhenParented) +#else + if (SwitchTransformSpaceWhenParented) +#endif { -#if COM_UNITY_MODULES_PHYSICS - var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); - var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); + m_PreviousNetworkObjectParent = m_CurrentNetworkObjectParent; + m_CurrentNetworkObjectParent = parentNetworkObject; + if (m_IsFirstNetworkTransform) + { + if (CanCommitToTransform) + { + InLocalSpace = m_CurrentNetworkObjectParent != null; + } + if (m_PreviousNetworkObjectParent && m_PreviousNetworkObjectParent.NetworkTransforms != null && m_PreviousNetworkObjectParent.NetworkTransforms.Count > 0) + { + // Always deregister with the first NetworkTransform in the list + m_PreviousNetworkObjectParent.NetworkTransforms[0].ChildRegistration(NetworkObject, false); + } + if (m_CurrentNetworkObjectParent && m_CurrentNetworkObjectParent.NetworkTransforms != null && m_CurrentNetworkObjectParent.NetworkTransforms.Count > 0) + { + // Always register with the first NetworkTransform in the list + m_CurrentNetworkObjectParent.NetworkTransforms[0].ChildRegistration(NetworkObject, true); + } + } + } + else + { + // Keep the same legacy behaviour for compatibility purposes + if (!CanCommitToTransform) + { +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); + var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); #else - var position = GetSpaceRelativePosition(); - var rotation = GetSpaceRelativeRotation(); + var position = GetSpaceRelativePosition(); + var rotation = GetSpaceRelativeRotation(); #endif - m_TargetPosition = m_CurrentPosition = position; - m_CurrentRotation = rotation; - m_TargetRotation = m_CurrentRotation.eulerAngles; - m_TargetScale = m_CurrentScale = GetScale(); + m_TargetPosition = m_InternalCurrentPosition = position; + m_InternalCurrentRotation = rotation; + m_TargetRotation = m_InternalCurrentRotation.eulerAngles; + m_TargetScale = m_InternalCurrentScale = GetScale(); - if (Interpolate) - { - m_ScaleInterpolator.Clear(); - m_PositionInterpolator.Clear(); - m_RotationInterpolator.Clear(); + if (Interpolate) + { + m_ScaleInterpolator.Clear(); + m_PositionInterpolator.Clear(); + m_RotationInterpolator.Clear(); - // Always use NetworkManager here as this can be invoked prior to spawning - var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, NetworkManager.ServerTime.Tick).Time; - UpdatePositionInterpolator(m_CurrentPosition, tempTime, true); - m_ScaleInterpolator.ResetTo(m_CurrentScale, tempTime); - m_RotationInterpolator.ResetTo(m_CurrentRotation, tempTime); + // Always use NetworkManager here as this can be invoked prior to spawning + var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, NetworkManager.ServerTime.Tick).Time; + UpdatePositionInterpolator(m_InternalCurrentPosition, tempTime, true); + m_ScaleInterpolator.ResetTo(m_InternalCurrentScale, tempTime); + m_RotationInterpolator.ResetTo(m_InternalCurrentRotation, tempTime); + } } } - base.OnNetworkObjectParentChanged(parentNetworkObject); + base.InternalOnNetworkObjectParentChanged(parentNetworkObject); } #endregion @@ -3212,7 +3492,7 @@ public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? s NetworkLog.LogError(errorMessage); return; } -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); #else @@ -3249,7 +3529,7 @@ public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? s ///
private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport) { -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if (m_UseRigidbodyForMotion) { m_NetworkRigidbodyInternal.SetPosition(pos); @@ -3349,10 +3629,12 @@ private void UpdateInterpolation() // Non-Authority if (Interpolate) { + AdjustForChangeInTransformSpace(); + var serverTime = m_CachedNetworkManager.ServerTime; var cachedServerTime = serverTime.Time; //var offset = (float)serverTime.TickOffset; -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; @@ -3360,14 +3642,10 @@ private void UpdateInterpolation() // With owner authoritative mode, non-authority clients can lag behind // by more than 1 tick period of time. The current "solution" for now // is to make their cachedRenderTime run 2 ticks behind. - var ticksAgo = (!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode ? 2 : 1; - // TODO: We need an RTT that updates regularly and not only when the client sends packets - //if (m_CachedNetworkManager.DistributedAuthorityMode) - //{ - //ticksAgo = m_CachedNetworkManager.CMBServiceConnection ? 2 : 3; - //ticksAgo = Mathf.Max(ticksAgo, (int)m_NetworkTransformTickRegistration.TicksAgo); - //offset = m_NetworkTransformTickRegistration.Offset; - //} + + // TODO: This could most likely just always be 2 + //var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; + var ticksAgo = 2; var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; @@ -3401,7 +3679,7 @@ private void UpdateInterpolation() public virtual void OnUpdate() { // If not spawned or this instance has authority, exit early -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D if (!IsSpawned || CanCommitToTransform || m_UseRigidbodyForMotion) #else if (!IsSpawned || CanCommitToTransform) @@ -3417,8 +3695,7 @@ public virtual void OnUpdate() ApplyAuthoritativeState(); } - -#if COM_UNITY_MODULES_PHYSICS +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 5e088c7f06..b5d549501a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -19,6 +19,12 @@ public RpcException(string message) : base(message) /// public abstract class NetworkBehaviour : MonoBehaviour { +#if UNITY_EDITOR + [HideInInspector] + [SerializeField] + internal bool ShowTopMostFoldoutHeaderGroup = true; +#endif + #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `public` @@ -688,6 +694,8 @@ public virtual void OnNetworkSpawn() { } /// protected virtual void OnNetworkPostSpawn() { } + protected internal virtual void InternalOnNetworkPostSpawn() { } + /// /// This method is only available client-side. /// When a new client joins it's synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all @@ -700,6 +708,8 @@ protected virtual void OnNetworkPostSpawn() { } /// protected virtual void OnNetworkSessionSynchronized() { } + protected internal virtual void InternalOnNetworkSessionSynchronized() { } + /// /// When a scene is loaded and in-scene placed NetworkObjects are finished spawning, this method is invoked on all of the newly spawned in-scene placed NetworkObjects. /// This method runs both client and server side. @@ -759,6 +769,7 @@ internal void NetworkPostSpawn() { try { + InternalOnNetworkPostSpawn(); OnNetworkPostSpawn(); } catch (Exception e) @@ -771,6 +782,7 @@ internal void NetworkSessionSynchronized() { try { + InternalOnNetworkSessionSynchronized(); OnNetworkSessionSynchronized(); } catch (Exception e) @@ -853,6 +865,8 @@ internal void InternalOnLostOwnership() /// the new parent public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { } + internal virtual void InternalOnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { } + private bool m_VarInit = false; private readonly List> m_DeliveryMappedNetworkVariableIndices = new List>(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b5a042c843..1239c93c7e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -17,6 +17,12 @@ namespace Unity.Netcode [AddComponentMenu("Netcode/Network Manager", -100)] public class NetworkManager : MonoBehaviour, INetworkUpdateSystem { +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool NetworkManagerExpanded; +#endif + // TODO: Deprecate... // The following internal values are not used, but because ILPP makes them public in the assembly, they cannot // be removed thanks to our semver validation. @@ -890,6 +896,11 @@ private void Reset() OnNetworkManagerReset?.Invoke(this); } + protected virtual void OnValidateComponent() + { + + } + internal void OnValidate() { if (NetworkConfig == null) @@ -950,6 +961,15 @@ internal void OnValidate() } } } + + try + { + OnValidateComponent(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } private void ModeChanged(PlayModeStateChange change) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 884ea748db..591fd417e7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -67,6 +67,7 @@ public uint PrefabIdHash /// public List NetworkTransforms { get; private set; } + #if COM_UNITY_MODULES_PHYSICS /// /// All component instances associated with a component instance. @@ -1080,6 +1081,24 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// public bool AutoObjectParentSync = true; + /// + /// Determines if the owner will apply transform values sent by the parenting message. + /// + /// + /// When enabled, the resultant parenting transform changes sent by the authority will be applied on all instances.
+ /// When disabled, the resultant parenting transform changes sent by the authority will not be applied on the owner's instance.
+ /// When disabled, all non-owner instances will still be synchronized by the authority's transform values when parented. + /// When using a network topology and an owner authoritative motion model, disabling this can help smooth parenting transitions. + /// When using a network topology this will have no impact on the owner's instance since only the authority/owner can parent. + ///
+ public bool SyncOwnerTransformWhenParented = true; + + /// + /// Client-Server specific, when enabled an owner of a NetworkObject can parent locally as opposed to requiring the owner to notify the server it would like to be parented. + /// This behavior is always true when using a distributed authority network topology and does not require it to be set. + /// + public bool AllowOwnerToParent; + internal readonly HashSet Observers = new HashSet(); #if MULTIPLAYER_TOOLS @@ -1787,6 +1806,9 @@ internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNe { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { + // Invoke internal notification + ChildNetworkBehaviours[i].InternalOnNetworkObjectParentChanged(parentNetworkObject); + // Invoke public notification ChildNetworkBehaviours[i].OnNetworkObjectParentChanged(parentNetworkObject); } } @@ -1918,7 +1940,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // DANGO-TODO: Do we want to worry about ownership permissions here? // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. - var isAuthority = HasAuthority; + var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner); // If we don't have authority and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. @@ -1984,7 +2006,7 @@ private void OnTransformParentChanged() var isAuthority = false; // With distributed authority, we need to track "valid authoritative" parenting changes. // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". - isAuthority = HasAuthority || AuthorityAppliedParenting; + isAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); var distributedAuthority = NetworkManager.DistributedAuthorityMode; // If we do not have authority and we are spawned @@ -2076,7 +2098,7 @@ private void OnTransformParentChanged() } // If we are connected to a CMB service or we are running a mock CMB service then send to the "server" identifier - if (distributedAuthority) + if (distributedAuthority || (!distributedAuthority && AllowOwnerToParent && IsOwner && !NetworkManager.IsServer)) { if (!NetworkManager.DAHost) { @@ -2365,7 +2387,9 @@ internal List ChildNetworkBehaviours { NetworkTransforms = new List(); } - NetworkTransforms.Add(networkBehaviours[i] as NetworkTransform); + var networkTransform = networkBehaviours[i] as NetworkTransform; + networkTransform.IsNested = i != 0 && networkTransform.gameObject != gameObject; + NetworkTransforms.Add(networkTransform); } #if COM_UNITY_MODULES_PHYSICS else if (type.IsSubclassOf(typeof(NetworkRigidbodyBase))) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index a55767d1d6..bef5fe8123 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -117,22 +117,28 @@ public void Handle(ref NetworkContext context) networkObject.SetNetworkParenting(LatestParent, WorldPositionStays); networkObject.ApplyNetworkParenting(RemoveParent); - // We set all of the transform values after parenting as they are - // the values of the server-side post-parenting transform values - if (!WorldPositionStays) + // This check is primarily for client-server network topologies when the motion model is owner authoritative: + // When SyncOwnerTransformWhenParented is enabled, then always apply the transform values. + // When SyncOwnerTransformWhenParented is disabled, then only synchronize the transform on non-owner instances. + if (networkObject.SyncOwnerTransformWhenParented || (!networkObject.SyncOwnerTransformWhenParented && !networkObject.IsOwner)) { - networkObject.transform.localPosition = Position; - networkObject.transform.localRotation = Rotation; - } - else - { - networkObject.transform.position = Position; - networkObject.transform.rotation = Rotation; + // We set all of the transform values after parenting as they are + // the values of the server-side post-parenting transform values + if (!WorldPositionStays) + { + networkObject.transform.localPosition = Position; + networkObject.transform.localRotation = Rotation; + } + else + { + networkObject.transform.position = Position; + networkObject.transform.rotation = Rotation; + } + networkObject.transform.localScale = Scale; } - networkObject.transform.localScale = Scale; // If in distributed authority mode and we are running a DAHost and this is the DAHost, then forward the parent changed message to any remaining clients - if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection && networkManager.DAHost) + if ((networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection && networkManager.DAHost) || (networkObject.AllowOwnerToParent && context.SenderId == networkObject.OwnerClientId && networkManager.IsServer)) { var size = 0; var message = this; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index da35c76fec..4413b73fee 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -200,9 +200,8 @@ protected override IEnumerator OnTearDown() ///
/// Determines if we are running as a server or host /// Determines if we are using server or owner authority - public NetworkTransformBase(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) + public NetworkTransformBase(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) : base(testWithHost) { - m_UseHost = testWithHost == HostOrServer.Host; m_Authority = authority; m_Precision = precision; m_RotationCompression = rotationCompression; @@ -376,6 +375,18 @@ protected bool AllChildObjectInstancesAreSpawned() return true; } + protected bool AllFirstLevelChildObjectInstancesHaveChild() + { + foreach (var instance in ChildObjectComponent.ClientInstances.Values) + { + if (instance.transform.parent == null) + { + return false; + } + } + return true; + } + protected bool AllChildObjectInstancesHaveChild() { foreach (var instance in ChildObjectComponent.ClientInstances.Values) @@ -398,6 +409,33 @@ protected bool AllChildObjectInstancesHaveChild() return true; } + protected bool AllFirstLevelChildObjectInstancesHaveNoParent() + { + foreach (var instance in ChildObjectComponent.ClientInstances.Values) + { + if (instance.transform.parent != null) + { + return false; + } + } + return true; + } + + protected bool AllSubChildObjectInstancesHaveNoParent() + { + if (ChildObjectComponent.HasSubChild) + { + foreach (var instance in ChildObjectComponent.ClientSubChildInstances.Values) + { + if (instance.transform.parent != null) + { + return false; + } + } + } + return true; + } + /// /// A wait condition specific method that assures the local space coordinates /// are not impacted by NetworkTransform when parented. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 7b9ac9935d..95ca7e0f20 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -101,6 +101,225 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTran } #if !MULTIPLAYER_TOOLS + + private void UpdateTransformLocal(Components.NetworkTransform networkTransformTestComponent) + { + networkTransformTestComponent.transform.localPosition += GetRandomVector3(0.5f, 2.0f); + var rotation = networkTransformTestComponent.transform.localRotation; + var eulerRotation = rotation.eulerAngles; + eulerRotation += GetRandomVector3(0.5f, 5.0f); + rotation.eulerAngles = eulerRotation; + networkTransformTestComponent.transform.localRotation = rotation; + } + + private void UpdateTransformWorld(Components.NetworkTransform networkTransformTestComponent) + { + networkTransformTestComponent.transform.position += GetRandomVector3(0.5f, 2.0f); + var rotation = networkTransformTestComponent.transform.rotation; + var eulerRotation = rotation.eulerAngles; + eulerRotation += GetRandomVector3(0.5f, 5.0f); + rotation.eulerAngles = eulerRotation; + networkTransformTestComponent.transform.rotation = rotation; + } + + /// + /// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies + /// + [Test] + public void SwitchTransformSpaceWhenParentedTest([Values(0.5f, 1.0f, 5.0f)] float scale) + { + m_UseParentingThreshold = true; + // Get the NetworkManager that will have authority in order to spawn with the correct authority + var isServerAuthority = m_Authority == Authority.ServerAuthority; + var authorityNetworkManager = m_ServerNetworkManager; + if (!isServerAuthority) + { + authorityNetworkManager = m_ClientNetworkManagers[0]; + } + + var childAuthorityNetworkManager = m_ClientNetworkManagers[0]; + if (!isServerAuthority) + { + childAuthorityNetworkManager = m_ServerNetworkManager; + } + + // Spawn a parent and children + ChildObjectComponent.HasSubChild = true; + // Modify our prefabs for this specific test + m_ParentObject.GetComponent().TickSyncChildren = true; + m_ChildObject.GetComponent().SwitchTransformSpaceWhenParented = true; + m_ChildObject.GetComponent().TickSyncChildren = true; + m_SubChildObject.GetComponent().SwitchTransformSpaceWhenParented = true; + m_SubChildObject.GetComponent().TickSyncChildren = true; + m_ChildObject.AllowOwnerToParent = true; + m_SubChildObject.AllowOwnerToParent = true; + + + var authoritySideParent = SpawnObject(m_ParentObject.gameObject, authorityNetworkManager).GetComponent(); + var authoritySideChild = SpawnObject(m_ChildObject.gameObject, childAuthorityNetworkManager).GetComponent(); + var authoritySideSubChild = SpawnObject(m_SubChildObject.gameObject, childAuthorityNetworkManager).GetComponent(); + + // Assure all of the child object instances are spawned before proceeding to parenting + var success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesAreSpawned); + Assert.True(success, "Timed out waiting for all child instances to be spawned!"); + + // Get the owner instance if in client-server mode with owner authority + if (m_Authority == Authority.OwnerAuthority && !m_DistributedAuthority) + { + authoritySideParent = s_GlobalNetworkObjects[authoritySideParent.OwnerClientId][authoritySideParent.NetworkObjectId]; + authoritySideChild = s_GlobalNetworkObjects[authoritySideChild.OwnerClientId][authoritySideChild.NetworkObjectId]; + authoritySideSubChild = s_GlobalNetworkObjects[authoritySideSubChild.OwnerClientId][authoritySideSubChild.NetworkObjectId]; + } + + // Get the authority parent and child instances + m_AuthorityParentObject = NetworkTransformTestComponent.AuthorityInstance.NetworkObject; + m_AuthorityChildObject = ChildObjectComponent.AuthorityInstance.NetworkObject; + m_AuthoritySubChildObject = ChildObjectComponent.AuthoritySubInstance.NetworkObject; + + // The child NetworkTransform will use world space when world position stays and + // local space when world position does not stay when parenting. + ChildObjectComponent.AuthorityInstance.UseHalfFloatPrecision = m_Precision == Precision.Half; + ChildObjectComponent.AuthorityInstance.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; + ChildObjectComponent.AuthorityInstance.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; + + ChildObjectComponent.AuthoritySubInstance.UseHalfFloatPrecision = m_Precision == Precision.Half; + ChildObjectComponent.AuthoritySubInstance.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; + ChildObjectComponent.AuthoritySubInstance.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; + + // Set whether we are interpolating or not + m_AuthorityParentNetworkTransform = m_AuthorityParentObject.GetComponent(); + m_AuthorityParentNetworkTransform.Interpolate = true; + m_AuthorityChildNetworkTransform = m_AuthorityChildObject.GetComponent(); + m_AuthorityChildNetworkTransform.Interpolate = true; + m_AuthoritySubChildNetworkTransform = m_AuthoritySubChildObject.GetComponent(); + m_AuthoritySubChildNetworkTransform.Interpolate = true; + + // Apply a scale to the parent object to make sure the scale on the child is properly updated on + // non-authority instances. + var halfScale = scale * 0.5f; + m_AuthorityParentObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale); + m_AuthorityChildObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale); + m_AuthoritySubChildObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale); + + // Allow one tick for authority to update these changes + TimeTravelAdvanceTick(); + success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches); + + Assert.True(success, "All transform values did not match prior to parenting!"); + + success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches); + + Assert.True(success, "All transform values did not match prior to parenting!"); + + // Move things around while parenting and removing the parent + // Not the absolute "perfect" test, but it validates the clients all synchronize + // parenting and transform values. + for (int i = 0; i < 30; i++) + { + // Provide two network ticks for interpolation to finalize + TimeTravelAdvanceTick(); + TimeTravelAdvanceTick(); + + // This validates each child instance has preserved their local space values + AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Connected_Clients); + + // This validates each sub-child instance has preserved their local space values + AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Connected_Clients); + // Parent while in motion + if (i == 5) + { + // Parent the child under the parent with the current world position stays setting + Assert.True(authoritySideChild.TrySetParent(authoritySideParent.transform), $"[Child][Client-{authoritySideChild.NetworkManagerOwner.LocalClientId}] Failed to set child's parent!"); + + // This waits for all child instances to be parented + success = WaitForConditionOrTimeOutWithTimeTravel(AllFirstLevelChildObjectInstancesHaveChild, 300); + Assert.True(success, "Timed out waiting for all instances to have parented a child!"); + } + + if (i == 10) + { + // Parent the sub-child under the child with the current world position stays setting + Assert.True(authoritySideSubChild.TrySetParent(authoritySideChild.transform), $"[Sub-Child][Client-{authoritySideSubChild.NetworkManagerOwner.LocalClientId}] Failed to set sub-child's parent!"); + + // This waits for all child instances to be parented + success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild, 300); + Assert.True(success, "Timed out waiting for all instances to have parented a child!"); + } + + if (i == 15) + { + // Verify that a late joining client will synchronize to the parented NetworkObjects properly + CreateAndStartNewClientWithTimeTravel(); + + // Assure all of the child object instances are spawned (basically for the newly connected client) + success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesAreSpawned, 300); + Assert.True(success, "Timed out waiting for all child instances to be spawned!"); + + // This waits for all child instances to be parented + success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild, 300); + Assert.True(success, "Timed out waiting for all instances to have parented a child!"); + + // This validates each child instance has preserved their local space values + AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Late_Join_Client); + + // This validates each sub-child instance has preserved their local space values + AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Late_Join_Client); + } + + if (i == 20) + { + // Remove the parent + Assert.True(authoritySideSubChild.TryRemoveParent(), $"[Sub-Child][Client-{authoritySideSubChild.NetworkManagerOwner.LocalClientId}] Failed to set sub-child's parent!"); + + // This waits for all child instances to have the parent removed + success = WaitForConditionOrTimeOutWithTimeTravel(AllSubChildObjectInstancesHaveNoParent, 300); + Assert.True(success, "Timed out waiting for all instances remove the parent!"); + } + + if (i == 25) + { + // Parent the child under the parent with the current world position stays setting + Assert.True(authoritySideChild.TryRemoveParent(), $"[Child][Client-{authoritySideChild.NetworkManagerOwner.LocalClientId}] Failed to remove parent!"); + + // This waits for all child instances to be parented + success = WaitForConditionOrTimeOutWithTimeTravel(AllFirstLevelChildObjectInstancesHaveNoParent, 300); + Assert.True(success, "Timed out waiting for all instances remove the parent!"); + } + UpdateTransformWorld(m_AuthorityParentNetworkTransform); + if (m_AuthorityChildNetworkTransform.InLocalSpace) + { + UpdateTransformLocal(m_AuthorityChildNetworkTransform); + } + else + { + UpdateTransformWorld(m_AuthorityChildNetworkTransform); + } + + if (m_AuthoritySubChildNetworkTransform.InLocalSpace) + { + UpdateTransformLocal(m_AuthoritySubChildNetworkTransform); + } + else + { + UpdateTransformWorld(m_AuthoritySubChildNetworkTransform); + } + } + + success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches, 300); + + Assert.True(success, "All transform values did not match prior to parenting!"); + + // Revert the modifications made for this specific test + m_ParentObject.GetComponent().TickSyncChildren = false; + m_ChildObject.GetComponent().SwitchTransformSpaceWhenParented = false; + m_ChildObject.GetComponent().TickSyncChildren = false; + m_ChildObject.AllowOwnerToParent = false; + m_SubChildObject.AllowOwnerToParent = false; + m_SubChildObject.GetComponent().SwitchTransformSpaceWhenParented = false; + m_SubChildObject.GetComponent().TickSyncChildren = false; + } + + /// /// Validates that transform values remain the same when a NetworkTransform is /// parented under another NetworkTransform under all of the possible axial conditions @@ -410,6 +629,7 @@ public void TestAuthoritativeTransformChangeOneAtATime([Values] TransformSpace t Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "server side pos should be zero at first"); // sanity check TimeTravelAdvanceTick(); + TimeTravelToNextTick(); m_AuthoritativeTransform.StatePushed = false; var nextPosition = GetRandomVector3(2f, 30f); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 44530a782c..c4921d1fed 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -299,16 +299,15 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() }, new List { m_ServerNetworkManager }); WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); - var percentChanged = 1f / 60f; AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position); AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation); AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position); AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation); AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale); @@ -316,11 +315,11 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position); AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation); AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position); AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation); AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); @@ -333,11 +332,11 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position); AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation); AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position); AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation); AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale); @@ -345,11 +344,11 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position); AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation); AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position); AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale); - AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation); AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 9c402ef60e..4e0cea8e53 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -63,14 +63,10 @@ public void StopMoving() private const int k_MaxThresholdFailures = 4; private int m_ExceededThresholdCount; - private void Update() + public override void OnUpdate() { base.OnUpdate(); - if (!IsSpawned || TestComplete) - { - return; - } // Check the position of the nested object on the client if (CheckPosition) @@ -92,6 +88,17 @@ private void Update() m_ExceededThresholdCount = 0; } } + } + + private void Update() + { + base.OnUpdate(); + + if (!IsSpawned || !CanCommitToTransform || TestComplete) + { + return; + } + // Move the nested object on the server if (IsMoving) @@ -136,7 +143,6 @@ private void Update() Assert.True(CanCommitToTransform, $"Using non-authority instance to update transform!"); transform.position = new Vector3(1000.0f, 1000.0f, 1000.0f); } - } } diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayer-SA.prefab b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayer-SA.prefab index 3361d528e0..eb8da63629 100644 --- a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayer-SA.prefab +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayer-SA.prefab @@ -26,13 +26,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 772585991204072682} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 4, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 296612175404815447} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &6327137497379236391 MeshRenderer: @@ -51,6 +51,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -96,6 +99,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0d8ad30fca3f9a240bdce16f0166033b, type: 3} m_Name: m_EditorClassIdentifier: + AuthorityMode: 0 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -105,23 +111,27 @@ MonoBehaviour: SyncScaleX: 1 SyncScaleY: 1 SyncScaleZ: 1 - SlerpPosition: 1 - UseQuaternionSynchronization: 1 - UseQuaternionCompression: 1 - UseHalfFloatPrecision: 1 PositionThreshold: 0.001 RotAngleThreshold: 0.01 ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 1 + UseHalfFloatPrecision: 1 InLocalSpace: 1 Interpolate: 1 + SlerpPosition: 1 + IsServerAuthority: 1 DebugTransform: 0 - IsServerAuthoritative: 1 LastUpdatedPosition: {x: 0, y: 0, z: 0} LastUpdatedScale: {x: 0, y: 0, z: 0} LastUpdatedRotation: {x: 0, y: 0, z: 0, w: 0} + PushedPosition: {x: 0, y: 0, z: 0} + PushedScale: {x: 0, y: 0, z: 0} + PushedRotation: {x: 0, y: 0, z: 0, w: 0} PreviousUpdatedPosition: {x: 0, y: 0, z: 0} PreviousUpdatedScale: {x: 0, y: 0, z: 0} PreviousUpdatedRotation: {x: 0, y: 0, z: 0, w: 0} + m_StatesToLog: 80 RotationSpeed: 7.4 RotateBasedOnDirection: 0 --- !u!1 &2771624607751045562 @@ -147,6 +157,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2771624607751045562} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -154,7 +165,6 @@ Transform: m_Children: - {fileID: 4974009855568796650} m_Father: {fileID: 296612175404815447} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &4147667212972069939 GameObject: @@ -180,13 +190,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 4147667212972069939} + serializedVersion: 2 m_LocalRotation: {x: 0.17364816, y: -0, z: -0, w: 0.9848078} m_LocalPosition: {x: 0, y: 8, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 296612175404815447} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 20, y: 0, z: 0} --- !u!20 &8372809022110481992 Camera: @@ -202,9 +212,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -257,13 +275,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6292214655028195304} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -4, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5485086383386216104} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &3062926429781172158 MeshRenderer: @@ -282,6 +300,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -327,6 +348,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0d8ad30fca3f9a240bdce16f0166033b, type: 3} m_Name: m_EditorClassIdentifier: + AuthorityMode: 0 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -336,23 +360,27 @@ MonoBehaviour: SyncScaleX: 1 SyncScaleY: 1 SyncScaleZ: 1 - SlerpPosition: 1 - UseQuaternionSynchronization: 1 - UseQuaternionCompression: 1 - UseHalfFloatPrecision: 1 PositionThreshold: 0.001 RotAngleThreshold: 0.01 ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 1 + UseHalfFloatPrecision: 1 InLocalSpace: 1 Interpolate: 1 + SlerpPosition: 1 + IsServerAuthority: 1 DebugTransform: 0 - IsServerAuthoritative: 1 LastUpdatedPosition: {x: 0, y: 0, z: 0} LastUpdatedScale: {x: 0, y: 0, z: 0} LastUpdatedRotation: {x: 0, y: 0, z: 0, w: 0} + PushedPosition: {x: 0, y: 0, z: 0} + PushedScale: {x: 0, y: 0, z: 0} + PushedRotation: {x: 0, y: 0, z: 0, w: 0} PreviousUpdatedPosition: {x: 0, y: 0, z: 0} PreviousUpdatedScale: {x: 0, y: 0, z: 0} PreviousUpdatedRotation: {x: 0, y: 0, z: 0, w: 0} + m_StatesToLog: 80 RotationSpeed: 7.4 RotateBasedOnDirection: 0 --- !u!1001 &8977898853425847701 @@ -360,6 +388,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: -745482209883575862, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, @@ -369,7 +398,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 947981134, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} propertyPath: GlobalObjectIdHash - value: 951099334 + value: 2547197533 objectReference: {fileID: 0} - target: {fileID: 3809075828520557319, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} @@ -469,6 +498,33 @@ PrefabInstance: - {fileID: 8685790303553767877, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} - {fileID: -745482209883575862, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} - {fileID: 3809075828520557319, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} + m_RemovedGameObjects: [] + m_AddedGameObjects: + - targetCorrespondingSourceObject: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + insertIndex: -1 + addedObject: {fileID: 1522619104359096714} + - targetCorrespondingSourceObject: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + insertIndex: -1 + addedObject: {fileID: 5485086383386216104} + - targetCorrespondingSourceObject: {fileID: 8685790303553767874, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + insertIndex: -1 + addedObject: {fileID: 7199215624742357828} + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + insertIndex: -1 + addedObject: {fileID: 4389916208190318681} + - targetCorrespondingSourceObject: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + insertIndex: -1 + addedObject: {fileID: 5376990732947334198} + - targetCorrespondingSourceObject: {fileID: 8685790303553767886, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, + type: 3} + insertIndex: -1 + addedObject: {fileID: 7431853297519116304} m_SourcePrefab: {fileID: 100100000, guid: 96e0a72e30d0c46c8a5c9a750e8f5807, type: 3} --- !u!4 &296612175404815447 stripped Transform: @@ -511,6 +567,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: dfb1af1a9249278438d2daa2877ee2ad, type: 3} m_Name: m_EditorClassIdentifier: + AuthorityMode: 0 + TickSyncChildren: 1 + UseUnreliableDeltas: 0 SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -520,23 +579,27 @@ MonoBehaviour: SyncScaleX: 1 SyncScaleY: 1 SyncScaleZ: 1 - SlerpPosition: 0 - UseQuaternionSynchronization: 1 - UseQuaternionCompression: 0 - UseHalfFloatPrecision: 1 PositionThreshold: 0.001 RotAngleThreshold: 0.001 ScaleThreshold: 0.001 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 1 InLocalSpace: 0 Interpolate: 1 + SlerpPosition: 0 + IsServerAuthority: 1 DebugTransform: 0 - IsServerAuthoritative: 1 LastUpdatedPosition: {x: 0, y: 0, z: 0} LastUpdatedScale: {x: 0, y: 0, z: 0} LastUpdatedRotation: {x: 0, y: 0, z: 0, w: 0} + PushedPosition: {x: 0, y: 0, z: 0} + PushedScale: {x: 0, y: 0, z: 0} + PushedRotation: {x: 0, y: 0, z: 0, w: 0} PreviousUpdatedPosition: {x: 0, y: 0, z: 0} PreviousUpdatedScale: {x: 0, y: 0, z: 0} PreviousUpdatedRotation: {x: 0, y: 0, z: 0, w: 0} + m_StatesToLog: 80 --- !u!114 &7431853297519116304 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs index 86ce689e3d..f731d6fe95 100644 --- a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs @@ -37,8 +37,11 @@ public void PlayerIsMoving(float movementDirection) if (IsSpawned && CanCommitToTransform) { var rotateDirection = RotateBasedOnDirection ? movementDirection * RotationSpeed : RotationSpeed; - - transform.RotateAround(m_RootParentTransform.position, transform.TransformDirection(Vector3.up), RotationSpeed); + // Just make sure we are set to local space for this test + if (InLocalSpace) + { + transform.RotateAround(m_RootParentTransform.position, transform.TransformDirection(Vector3.up), RotationSpeed); + } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 8a19fc22f4..0cae4ecb12 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -9,18 +9,31 @@ namespace TestProject.RuntimeTests { - [TestFixture(Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server)] - [TestFixture(Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner)] - [TestFixture(Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server)] - [TestFixture(Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner)] - [TestFixture(Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server)] - [TestFixture(Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner)] - [TestFixture(Interpolation.NoInterpolation, Precision.Full, NetworkTransform.AuthorityModes.Server)] - [TestFixture(Interpolation.NoInterpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner)] - [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Server)] - [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner)] - [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server)] - [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner)] + [TestFixture(Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.NoInterpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.NoInterpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + + [TestFixture(Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.NoInterpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.NoInterpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] public class NestedNetworkTransformTests : IntegrationTestWithApproximation { private const string k_TestScene = "NestedNetworkTransformTestScene"; @@ -41,6 +54,7 @@ public class NestedNetworkTransformTests : IntegrationTestWithApproximation private Interpolation m_Interpolation; private Precision m_Precision; private NetworkTransform.AuthorityModes m_Authority; + private NestedTickSynchronization m_NestedTickSynchronization; public enum Interpolation { @@ -61,12 +75,19 @@ public enum AuthoritativeModel Owner } + public enum NestedTickSynchronization + { + NormalSynchronize, + TickSynchronized, + } + - public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel) + public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) { m_Interpolation = interpolation; m_Precision = precision; m_Authority = authoritativeModel; + m_NestedTickSynchronization = nestedTickSynchronization; } public NestedNetworkTransformTests() @@ -136,6 +157,7 @@ private void ConfigureNetworkTransform(IntegrationNetworkTransform networkTransf networkTransform.UseHalfFloatPrecision = m_Precision == Precision.Half || m_Precision == Precision.Compressed; networkTransform.UseQuaternionCompression = m_Precision == Precision.Compressed; networkTransform.AuthorityMode = m_Authority; + networkTransform.TickSyncChildren = m_NestedTickSynchronization == NestedTickSynchronization.TickSynchronized; } @@ -152,6 +174,12 @@ protected override void OnCreatePlayerPrefab() foreach (var networkTransform in networkTransforms) { ConfigureNetworkTransform(networkTransform); + if (networkTransform.TickSyncChildren && networkTransform.gameObject != m_PlayerPrefab) + { + // Skew the thresholds of the children + networkTransform.PositionThreshold *= Random.Range(0.75f, 0.90f); + networkTransform.RotAngleThreshold *= Random.Range(0.75f, 0.90f); + } } base.OnCreatePlayerPrefab(); From c0cd658265be297bc5b4a1997632b2256c1c8bc7 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 6 Sep 2024 17:34:05 -0500 Subject: [PATCH 091/236] fix: player prefab null exception when using prefab hash via connection approval (#3042) * fix fixing the issue where setting the prefab hash but not assigning a player prefab would cause an exception. * update change log entry * test Updating the ConnectionApproval test to include spawning from player prefab, prefab hash, spawning no player, and failing validation. * update updating changelog with PR number. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Connection/NetworkConnectionManager.cs | 34 +---- .../Runtime/NetcodeIntegrationTest.cs | 18 ++- .../Tests/Runtime/ConnectionApproval.cs | 137 +++++++++++++----- 4 files changed, 125 insertions(+), 65 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 47d74d92fd..f791d42fd6 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -19,6 +19,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where setting a prefab hash value during connection approval but not having a player prefab assigned could cause an exception when spawning a player. (#3042) - Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030) - Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026) - Fixed issue with newly/late joined clients and `NetworkTransform` synchronization of parented `NetworkObject` instances. (#3013) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 722fbb24b5..03d64cb1fc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -735,42 +735,23 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne RemovePendingClient(ownerClientId); var client = AddClient(ownerClientId); - if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && NetworkManager.NetworkConfig.PlayerPrefab != null) - { - var prefabNetworkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); - var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash; - - // Generate a SceneObject for the player object to spawn - // Note: This is only to create the local NetworkObject, many of the serialized properties of the player prefab will be set when instantiated. - var sceneObject = new NetworkObject.SceneObject - { - OwnerClientId = ownerClientId, - IsPlayerObject = true, - IsSceneObject = false, - HasTransform = prefabNetworkObject.SynchronizeTransform, - Hash = playerPrefabHash, - TargetClientId = ownerClientId, - DontDestroyWithOwner = prefabNetworkObject.DontDestroyWithOwner, - Transform = new NetworkObject.SceneObject.TransformData - { - Position = response.Position.GetValueOrDefault(), - Rotation = response.Rotation.GetValueOrDefault() - } - }; - // Create the player NetworkObject locally - var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(sceneObject); + // Server-side spawning (only if there is a prefab hash or player prefab provided) + if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null)) + { + var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault()) + : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault()); // Spawn the player NetworkObject locally NetworkManager.SpawnManager.SpawnNetworkObjectLocally( - networkObject, + playerObject, NetworkManager.SpawnManager.GetNetworkObjectId(), sceneObject: false, playerObject: true, ownerClientId, destroyWithScene: false); - client.AssignPlayerObject(ref networkObject); + client.AssignPlayerObject(ref playerObject); } // Server doesn't send itself the connection approved message @@ -871,6 +852,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne } } + // Exit early if no player object was spawned if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkManager.NetworkConfig.PlayerPrefab == null)) { return; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 216569355c..70ed7842f5 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -850,6 +850,12 @@ protected void ClientNetworkManagerPostStartInit() protected virtual bool LogAllMessages => false; + protected virtual bool ShouldCheckForSpawnedPlayers() + { + return true; + } + + /// /// This starts the server and clients as long as /// returns true. @@ -938,7 +944,12 @@ protected IEnumerator StartServerAndClients() AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{m_ServerNetworkManager.LocalClientId}'s player object!\n {m_InternalErrorLog}"); } } - ClientNetworkManagerPostStartInit(); + + if (ShouldCheckForSpawnedPlayers()) + { + ClientNetworkManagerPostStartInit(); + } + // Notification that at this time the server and client(s) are instantiated, // started, and connected on both sides. yield return OnServerAndClientsConnected(); @@ -1030,7 +1041,10 @@ protected void StartServerAndClientsWithTimeTravel() } } - ClientNetworkManagerPostStartInit(); + if (ShouldCheckForSpawnedPlayers()) + { + ClientNetworkManagerPostStartInit(); + } // Notification that at this time the server and client(s) are instantiated, // started, and connected on both sides. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs index ae606752d2..6fd311ffa9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs @@ -1,65 +1,135 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; -using UnityEngine; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { - internal class ConnectionApprovalTests + [TestFixture(PlayerCreation.Prefab)] + [TestFixture(PlayerCreation.PrefabHash)] + [TestFixture(PlayerCreation.NoPlayer)] + [TestFixture(PlayerCreation.FailValidation)] + internal class ConnectionApprovalTests : NetcodeIntegrationTest { + private const string k_InvalidToken = "Invalid validation token!"; + public enum PlayerCreation + { + Prefab, + PrefabHash, + NoPlayer, + FailValidation + } + private PlayerCreation m_PlayerCreation; + private bool m_ClientDisconnectReasonValidated; + + private Dictionary m_Validated = new Dictionary(); + + public ConnectionApprovalTests(PlayerCreation playerCreation) + { + m_PlayerCreation = playerCreation; + } + + protected override int NumberOfClients => 1; + private Guid m_ValidationToken; - private bool m_IsValidated; - [SetUp] - public void Setup() + protected override bool ShouldCheckForSpawnedPlayers() + { + return m_PlayerCreation != PlayerCreation.NoPlayer; + } + + protected override void OnServerAndClientsCreated() { - // Create, instantiate, and host - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None)); + m_ClientDisconnectReasonValidated = false; + m_BypassConnectionTimeout = m_PlayerCreation == PlayerCreation.FailValidation; + m_Validated.Clear(); m_ValidationToken = Guid.NewGuid(); + var validationToken = Encoding.UTF8.GetBytes(m_ValidationToken.ToString()); + m_ServerNetworkManager.ConnectionApprovalCallback = NetworkManagerObject_ConnectionApprovalCallback; + m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerCreation == PlayerCreation.Prefab ? m_PlayerPrefab : null; + if (m_PlayerCreation == PlayerCreation.PrefabHash) + { + m_ServerNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = m_PlayerPrefab }); + } + m_ServerNetworkManager.NetworkConfig.ConnectionApproval = true; + m_ServerNetworkManager.NetworkConfig.ConnectionData = validationToken; + + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.PlayerPrefab = m_PlayerCreation == PlayerCreation.Prefab ? m_PlayerPrefab : null; + if (m_PlayerCreation == PlayerCreation.PrefabHash) + { + client.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = m_PlayerPrefab }); + } + client.NetworkConfig.ConnectionApproval = true; + client.NetworkConfig.ConnectionData = m_PlayerCreation == PlayerCreation.FailValidation ? Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()) : validationToken; + if (m_PlayerCreation == PlayerCreation.FailValidation) + { + client.OnClientDisconnectCallback += Client_OnClientDisconnectCallback; + } + } + + base.OnServerAndClientsCreated(); } - [UnityTest] - public IEnumerator ConnectionApproval() + private void Client_OnClientDisconnectCallback(ulong clientId) + { + m_ClientNetworkManagers[0].OnClientDisconnectCallback -= Client_OnClientDisconnectCallback; + m_ClientDisconnectReasonValidated = m_ClientNetworkManagers[0].LocalClientId == clientId && m_ClientNetworkManagers[0].DisconnectReason == k_InvalidToken; + } + + private bool ClientAndHostValidated() { - NetworkManagerHelper.NetworkManagerObject.ConnectionApprovalCallback = NetworkManagerObject_ConnectionApprovalCallback; - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionApproval = true; - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.PlayerPrefab = null; - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionData = Encoding.UTF8.GetBytes(m_ValidationToken.ToString()); - m_IsValidated = false; - NetworkManagerHelper.NetworkManagerObject.StartHost(); - - var timeOut = Time.realtimeSinceStartup + 3.0f; - var timedOut = false; - while (!m_IsValidated) + if (!m_Validated.ContainsKey(m_ServerNetworkManager.LocalClientId) || !m_Validated[m_ServerNetworkManager.LocalClientId]) { - yield return new WaitForSeconds(0.01f); - if (timeOut < Time.realtimeSinceStartup) + return false; + } + if (m_PlayerCreation == PlayerCreation.FailValidation) + { + return m_ClientDisconnectReasonValidated; + } + else + { + foreach (var client in m_ClientNetworkManagers) { - timedOut = true; + if (!m_Validated.ContainsKey(client.LocalClientId) || !m_Validated[client.LocalClientId]) + { + return false; + } } } + return true; + } - //Make sure we didn't time out - Assert.False(timedOut); - Assert.True(m_IsValidated); + [UnityTest] + public IEnumerator ConnectionApproval() + { + yield return WaitForConditionOrTimeOut(ClientAndHostValidated); + AssertOnTimeout("Timed out waiting for all clients to be approved!"); } private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) { var stringGuid = Encoding.UTF8.GetString(request.Payload); + if (m_ValidationToken.ToString() == stringGuid) { - m_IsValidated = true; + m_Validated.Add(request.ClientNetworkId, true); + response.Approved = true; + } + else + { + response.Approved = false; + response.Reason = "Invalid validation token!"; } - response.Approved = m_IsValidated; - response.CreatePlayerObject = false; + response.CreatePlayerObject = ShouldCheckForSpawnedPlayers(); response.Position = null; response.Rotation = null; - response.PlayerPrefabHash = null; + response.PlayerPrefabHash = m_PlayerCreation == PlayerCreation.PrefabHash ? m_PlayerPrefab.GetComponent().GlobalObjectIdHash : null; } @@ -78,13 +148,6 @@ public void VerifyUniqueNetworkConfigPerRequest() Assert.True(currentHash != newHash, $"Hashed {nameof(NetworkConfig)} values {currentHash} and {newHash} should not be the same!"); } - - [TearDown] - public void TearDown() - { - // Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - } - } } + From 8e62d9e4bb14a3a43e0f2bffc8c80a2ed558eb9a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 7 Sep 2024 10:21:20 -0500 Subject: [PATCH 092/236] fix: Add size validation to named and unnamed message sending [Up Port] (#3049) * fix Adding the fixes from #3043 * test Adding the tests from #3043 * update Adding changelog entry * fix Check for CustomMessageManager existing before invoking. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Messaging/CustomMessageManager.cs | 34 ++++++++++++++++ .../Messaging/Messages/UnnamedMessage.cs | 2 +- .../Messaging/NetworkMessageManager.cs | 6 ++- .../Runtime/Serialization/FastBufferWriter.cs | 4 +- .../Runtime/Messaging/NamedMessageTests.cs | 40 +++++++++++++++++++ .../Runtime/Messaging/UnnamedMessageTests.cs | 39 ++++++++++++++++++ 7 files changed, 122 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f791d42fd6..f3b9c50ffd 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added message size validation to named and unnamed message sending functions for better error messages. (#3049) - Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3031) - Added `NetworkTransform.SwitchTransformSpaceWhenParented` property that, when enabled, will handle the world to local, local to world, and local to local transform space transitions when interpolation is enabled. (#3013) - Added `NetworkTransform.TickSyncChildren` that, when enabled, will tick synchronize nested and/or child `NetworkTransform` components to eliminate any potential visual jittering that could occur if the `NetworkTransform` instances get into a state where their state updates are landing on different network ticks. (#3013) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index dfea03e53f..9a83c6dcc5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -95,6 +95,7 @@ public void SendUnnamedMessage(IReadOnlyList clientIds, FastBufferWriter return; } + ValidateMessageSize(messageBuffer, networkDelivery, isNamed: false); if (m_NetworkManager.IsHost) { @@ -131,6 +132,8 @@ public void SendUnnamedMessage(IReadOnlyList clientIds, FastBufferWriter /// The delivery type (QoS) to send data with public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { + ValidateMessageSize(messageBuffer, networkDelivery, isNamed: false); + if (m_NetworkManager.IsHost) { if (clientId == m_NetworkManager.LocalClientId) @@ -286,6 +289,8 @@ public void SendNamedMessageToAll(string messageName, FastBufferWriter messageSt /// The delivery type (QoS) to send data with public void SendNamedMessage(string messageName, ulong clientId, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { + ValidateMessageSize(messageStream, networkDelivery, isNamed: true); + ulong hash = 0; switch (m_NetworkManager.NetworkConfig.RpcHashSize) { @@ -367,6 +372,8 @@ public void SendNamedMessage(string messageName, IReadOnlyList clientIds, return; } + ValidateMessageSize(messageStream, networkDelivery, isNamed: true); + ulong hash = 0; switch (m_NetworkManager.NetworkConfig.RpcHashSize) { @@ -405,5 +412,32 @@ public void SendNamedMessage(string messageName, IReadOnlyList clientIds, m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientIds, messageName, size); } } + + /// + /// Validate the size of the message. If it's a non-fragmented delivery type the message must fit within the + /// max allowed size with headers also subtracted. Named messages also include the hash + /// of the name string. Only validates in editor and development builds. + /// + /// The named message payload + /// Delivery method + /// Is the message named (or unnamed) + /// Exception thrown in case validation fails + private unsafe void ValidateMessageSize(FastBufferWriter messageStream, NetworkDelivery networkDelivery, bool isNamed) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + var maxNonFragmentedSize = m_NetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize() - sizeof(NetworkBatchHeader); + if (isNamed) + { + maxNonFragmentedSize -= sizeof(ulong); // MessageName hash + } + if (networkDelivery != NetworkDelivery.ReliableFragmentedSequenced + && messageStream.Length > maxNonFragmentedSize) + { + throw new OverflowException($"Given message size ({messageStream.Length} bytes) is greater than " + + $"the maximum allowed for the selected delivery method ({maxNonFragmentedSize} bytes). Try using " + + $"ReliableFragmentedSequenced delivery method instead."); + } +#endif + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs index ccce67c3f5..c5c0b031e0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/UnnamedMessage.cs @@ -20,7 +20,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { - ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize); + ((NetworkManager)context.SystemOwner).CustomMessagingManager?.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index d42251e794..1850725599 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -733,7 +733,11 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t } ref var writeQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1); - writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length); + if (!writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length)) + { + Debug.LogError($"Not enough space to write message, size={tmpSerializer.Length + headerSerializer.Length} space used={writeQueueItem.Writer.Position} total size={writeQueueItem.Writer.Capacity}"); + continue; + } writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length); writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index b2a43a0a4b..66d0da5b17 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -700,7 +700,7 @@ public unsafe void WriteBytes(byte* value, int size, int offset = 0) } if (Handle->Position + size > Handle->AllowedWriteMark) { - throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()"); + throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}(), Position+Size={Handle->Position + size} > AllowedWriteMark={Handle->AllowedWriteMark}"); } #endif UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size); @@ -729,7 +729,7 @@ public unsafe void WriteBytesSafe(byte* value, int size, int offset = 0) if (!TryBeginWriteInternal(size)) { - throw new OverflowException("Writing past the end of the buffer"); + throw new OverflowException($"Writing past the end of the buffer, size is {size} bytes but remaining capacity is {Handle->Capacity - Handle->Position} bytes"); } UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size); Handle->Position += size; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs index 541fbb2fce..4c2d0d8cfd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs @@ -239,5 +239,45 @@ public void WhenSendingNamedMessageToNullClientList_ArgumentNullExceptionIsThrow }); } } + + [Test] + public unsafe void ErrorMessageIsPrintedWhenAttemptingToSendNamedMessageWithTooBigBuffer() + { + // First try a valid send with the maximum allowed size (this is atm 1264) + var msgSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize() - sizeof(ulong)/*MessageName hash*/ - sizeof(NetworkBatchHeader); + var bufferSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize; + var messageName = Guid.NewGuid().ToString(); + var messageContent = new byte[msgSize]; + var writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2); + using (writer) + { + writer.TryBeginWrite(msgSize); + writer.WriteBytes(messageContent, msgSize, 0); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, new List { FirstClient.LocalClientId }, writer); + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, writer); + } + + msgSize++; + messageContent = new byte[msgSize]; + writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2); + using (writer) + { + writer.TryBeginWrite(msgSize); + writer.WriteBytes(messageContent, msgSize, 0); + var message = Assert.Throws( + () => + { + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, new List { FirstClient.LocalClientId }, writer); + }).Message; + Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}"); + + message = Assert.Throws( + () => + { + m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, writer); + }).Message; + Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}"); + } + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs index 61333ad003..8b0c854b59 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs @@ -194,5 +194,44 @@ public void WhenSendingNamedMessageToNullClientList_ArgumentNullExceptionIsThrow }); } } + + [Test] + public unsafe void ErrorMessageIsPrintedWhenAttemptingToSendUnnamedMessageWithTooBigBuffer() + { + // First try a valid send with the maximum allowed size (this is atm 1272) + var msgSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize() - sizeof(NetworkBatchHeader); + var bufferSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize; + var messageContent = new byte[msgSize]; + var writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2); + using (writer) + { + writer.TryBeginWrite(msgSize); + writer.WriteBytes(messageContent, msgSize, 0); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(new List { FirstClient.LocalClientId }, writer); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer); + } + + msgSize++; + messageContent = new byte[msgSize]; + writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2); + using (writer) + { + writer.TryBeginWrite(msgSize); + writer.WriteBytes(messageContent, msgSize, 0); + var message = Assert.Throws( + () => + { + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(new List { FirstClient.LocalClientId }, writer); + }).Message; + Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}"); + + message = Assert.Throws( + () => + { + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer); + }).Message; + Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}"); + } + } } } From 91bb80eaff8bccac55936acd1a0b84ca1e2ee522 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 7 Sep 2024 13:45:28 -0500 Subject: [PATCH 093/236] fix: hidden objects from newly promoted session owner still synchronize with newly joining clients (#3051) * fix This fixes the issue where a NetworkObject hidden from a client that is promoted to session owner will still be synchronized with newly joining clients. * test The test to validate the fix * update adding changelog entry * style Minor typo --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Connection/NetworkConnectionManager.cs | 8 + .../Runtime/Core/NetworkManager.cs | 1 - .../Messages/ClientConnectedMessage.cs | 6 + .../SceneManagement/NetworkSceneManager.cs | 39 ++--- .../Runtime/Spawning/NetworkSpawnManager.cs | 50 +++++++ .../ExtendedNetworkShowAndHideTests.cs | 139 ++++++++++++++++++ .../ExtendedNetworkShowAndHideTests.cs.meta | 2 + 8 files changed, 228 insertions(+), 18 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f3b9c50ffd..2aeef42809 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -20,6 +20,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where a NetworkObject hidden from a client that is then promoted to be session owner was not being synchronized with newly joining clients.(#3051) - Fixed issue where setting a prefab hash value during connection approval but not having a player prefab assigned could cause an exception when spawning a player. (#3042) - Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030) - Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 03d64cb1fc..af97de8d06 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -985,10 +985,18 @@ internal NetworkClient AddClient(ulong clientId) ConnectedClientIds.Add(clientId); } + var distributedAuthority = NetworkManager.DistributedAuthorityMode; + var sessionOwnerId = NetworkManager.CurrentSessionOwner; + var isSessionOwner = NetworkManager.LocalClient.IsSessionOwner; foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) { if (networkObject.SpawnWithObservers) { + // Don't add the client to the observers if hidden from the session owner + if (networkObject.IsOwner && distributedAuthority && !isSessionOwner && !networkObject.Observers.Contains(sessionOwnerId)) + { + continue; + } networkObject.Observers.Add(clientId); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 1239c93c7e..0cdb995128 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -195,7 +195,6 @@ internal void SetSessionOwner(ulong sessionOwner) OnSessionOwnerPromoted?.Invoke(sessionOwner); } - // TODO: Make this internal after testing internal void PromoteSessionOwner(ulong clientId) { if (!DistributedAuthorityMode) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index 0a1b1aeeb4..d1cd7e43eb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -55,6 +55,12 @@ public void Handle(ref NetworkContext context) // Don't redistribute for the local instance if (ClientId != networkManager.LocalClientId) { + // Show any NetworkObjects that are: + // - Hidden from the session owner + // - Owned by this client + // - Has NetworkObject.SpawnWithObservers set to true (the default) + networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId); + // We defer redistribution to the end of the NetworkUpdateStage.PostLateUpdate networkManager.RedistributeToClient = true; networkManager.ClientToRedistribute = ClientId; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 975697862b..65222007bf 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2550,17 +2550,6 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) // At this point the client is considered fully "connected" if ((NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) || !NetworkManager.DistributedAuthorityMode) { - if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) - { - // DANGO-EXP TODO: Remove this once service is sending the synchronization message to all clients - if (NetworkManager.ConnectedClients.ContainsKey(clientId) && NetworkManager.ConnectionManager.ConnectedClientIds.Contains(clientId) && NetworkManager.ConnectedClientsList.Contains(NetworkManager.ConnectedClients[clientId])) - { - EndSceneEvent(sceneEventId); - return; - } - NetworkManager.ConnectionManager.AddClient(clientId); - } - // Notify the local server that a client has finished synchronizing OnSceneEvent?.Invoke(new SceneEvent() { @@ -2575,6 +2564,20 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) } else { + // Notify the local server that a client has finished synchronizing + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventData.SceneEventType, + SceneName = string.Empty, + ClientId = clientId + }); + + // Show any NetworkObjects that are: + // - Hidden from the session owner + // - Owned by this client + // - Has NetworkObject.SpawnWithObservers set to true (the default) + NetworkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(clientId); + // DANGO-EXP TODO: Remove this once service distributes objects // Non-session owners receive this notification from newly connected clients and upon receiving // the event they will redistribute their NetworkObjects @@ -2589,9 +2592,6 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) // At this time the client is fully synchronized with all loaded scenes and // NetworkObjects and should be considered "fully connected". Send the // notification that the client is connected. - // TODO 2023: We should have a better name for this or have multiple states the - // client progresses through (the name and associated legacy behavior/expected state - // of the client was persisted since MLAPI) NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(clientId); if (NetworkManager.IsHost) @@ -2664,9 +2664,14 @@ internal void HandleSceneEvent(ulong clientId, FastBufferReader reader) EventData = sceneEventData, }; // Forward synchronization to client then exit early because DAHost is not the current session owner - NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.CurrentSessionOwner); - EndSceneEvent(sceneEventData.SceneEventId); - return; + foreach (var client in NetworkManager.ConnectedClientsIds) + { + if (client == NetworkManager.LocalClientId) + { + continue; + } + NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, client); + } } } else diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 4a7ea434ec..d3b2f9acd6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1909,5 +1909,55 @@ internal void NotifyNetworkObjectsSynchronized() networkObject.InternalNetworkSessionSynchronized(); } } + + /// + /// Distributed Authority Only + /// Should be invoked on non-session owner clients when a newly joined client is finished + /// synchronizing in order to "show" (spawn) anything that might be currently hidden from + /// the session owner. + /// + internal void ShowHiddenObjectsToNewlyJoinedClient(ulong newClientId) + { + if (!NetworkManager.DistributedAuthorityMode) + { + if (NetworkManager == null || !NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} invoked while !"); + } + return; + } + + if (!NetworkManager.DistributedAuthorityMode) + { + Debug.LogError($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} should only be invoked when using a distributed authority network topology!"); + return; + } + + if (NetworkManager.LocalClient.IsSessionOwner) + { + Debug.LogError($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} should only be invoked on a non-session owner client!"); + return; + } + var localClientId = NetworkManager.LocalClient.ClientId; + var sessionOwnerId = NetworkManager.CurrentSessionOwner; + foreach (var networkObject in SpawnedObjectsList) + { + if (networkObject.SpawnWithObservers && networkObject.OwnerClientId == localClientId && !networkObject.Observers.Contains(sessionOwnerId)) + { + if (networkObject.Observers.Contains(newClientId)) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + // Track if there is some other location where the client is being added to the observers list when the object is hidden from the session owner + Debug.LogWarning($"[{networkObject.name}] Has new client as an observer but it is hidden from the session owner!"); + } + // For now, remove the client (impossible for the new client to have an instance since the session owner doesn't) to make sure newly added + // code to handle this edge case works. + networkObject.Observers.Remove(newClientId); + } + networkObject.NetworkShow(newClientId); + } + } + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs new file mode 100644 index 0000000000..61d71c0d4d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs @@ -0,0 +1,139 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.DAHost)] + public class ExtendedNetworkShowAndHideTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 3; + + private GameObject m_ObjectToSpawn; + private NetworkObject m_SpawnedObject; + private NetworkManager m_ClientToHideFrom; + private NetworkManager m_LateJoinClient; + private NetworkManager m_SpawnOwner; + + public ExtendedNetworkShowAndHideTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + protected override void OnServerAndClientsCreated() + { + m_ObjectToSpawn = CreateNetworkObjectPrefab("TestObject"); + m_ObjectToSpawn.SetActive(false); + base.OnServerAndClientsCreated(); + } + + private bool AllClientsSpawnedObject() + { + if (!UseCMBService()) + { + if (!s_GlobalNetworkObjects.ContainsKey(m_ServerNetworkManager.LocalClientId)) + { + return false; + } + if (!s_GlobalNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_SpawnedObject.NetworkObjectId)) + { + return false; + } + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId)) + { + return false; + } + if (!s_GlobalNetworkObjects[client.LocalClientId].ContainsKey(m_SpawnedObject.NetworkObjectId)) + { + return false; + } + } + return true; + } + + private bool IsClientPromotedToSessionOwner() + { + if (!UseCMBService()) + { + if (m_ServerNetworkManager.CurrentSessionOwner != m_ClientToHideFrom.LocalClientId) + { + return false; + } + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!client.IsConnectedClient) + { + continue; + } + if (client.CurrentSessionOwner != m_ClientToHideFrom.LocalClientId) + { + return false; + } + } + return true; + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + m_LateJoinClient = networkManager; + + networkManager.NetworkConfig.Prefabs = m_SpawnOwner.NetworkConfig.Prefabs; + base.OnNewClientCreated(networkManager); + } + + /// + /// This test validates the following NetworkShow - NetworkHide issue: + /// - During a session, a spawned object is hidden from a client. + /// - The current session owner disconnects and the client the object is hidden from is prommoted to the session owner. + /// - A new client joins and the newly promoted session owner synchronizes the newly joined client with only objects visible to it. + /// - Any already connected non-session owner client should "NetworkShow" the object to the newly connected client + /// (but only if the hidden object has SpawnWithObservers enabled) + /// + [UnityTest] + public IEnumerator HiddenObjectPromotedSessionOwnerNewClientSynchronizes() + { + // Get the test relative session owner + var sessionOwner = UseCMBService() ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; + m_SpawnOwner = UseCMBService() ? m_ClientNetworkManagers[1] : m_ClientNetworkManagers[0]; + m_ClientToHideFrom = UseCMBService() ? m_ClientNetworkManagers[NumberOfClients - 1] : m_ClientNetworkManagers[1]; + m_ObjectToSpawn.SetActive(true); + + // Spawn the object with a non-session owner client + m_SpawnedObject = SpawnObject(m_ObjectToSpawn, m_SpawnOwner).GetComponent(); + yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject); + AssertOnTimeout($"Not all clients spawned and instance of {m_SpawnedObject.name}"); + + // Hide the spawned object from the to be promoted session owner + m_SpawnedObject.NetworkHide(m_ClientToHideFrom.LocalClientId); + + yield return WaitForConditionOrTimeOut(() => !m_ClientToHideFrom.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedObject.NetworkObjectId)); + AssertOnTimeout($"{m_SpawnedObject.name} was not hidden from Client-{m_ClientToHideFrom.LocalClientId}!"); + + // Promoted a new session owner (DAHost promotes while CMB Session we disconnect the current session owner) + if (!UseCMBService()) + { + m_ServerNetworkManager.PromoteSessionOwner(m_ClientToHideFrom.LocalClientId); + } + else + { + sessionOwner.Shutdown(); + } + + // Wait for the new session owner to be promoted and for all clients to acknowledge the promotion + yield return WaitForConditionOrTimeOut(IsClientPromotedToSessionOwner); + AssertOnTimeout($"Client-{m_ClientToHideFrom.LocalClientId} was not promoted as session owner on all client instances!"); + + // Connect a new client instance + yield return CreateAndStartNewClient(); + + // Assure the newly connected client is synchronized with the NetworkObject hidden from the newly promoted session owner + yield return WaitForConditionOrTimeOut(() => m_LateJoinClient.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedObject.NetworkObjectId)); + AssertOnTimeout($"Client-{m_LateJoinClient.LocalClientId} never spawned {nameof(NetworkObject)} {m_SpawnedObject.name}!"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs.meta new file mode 100644 index 0000000000..5d227513b6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a6389d04d9080b24b99de7e6900a064c \ No newline at end of file From 4b58d28c62248e0128b9924053ed0c07c52d1e5b Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 8 Sep 2024 14:36:44 -0500 Subject: [PATCH 094/236] fix: NetworkVariable LastUpdateSent client timing (#3045) * fix Use server time for NetworkVariableBase.LastUpdateSent time deltas when connected to a service or when not the server. * update Adding changelog entry * update adding PR number to entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/NetworkVariable/NetworkVariableBase.cs | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2aeef42809..d618645fdc 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where a NetworkObject hidden from a client that is then promoted to be session owner was not being synchronized with newly joining clients.(#3051) +- Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045) - Fixed issue where setting a prefab hash value during connection approval but not having a player prefab assigned could cause an exception when spawning a player. (#3042) - Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030) - Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index a048cea2ca..75ce48aa1c 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -187,7 +187,9 @@ public virtual void SetDirty(bool isDirty) internal bool CanSend() { - var timeSinceLastUpdate = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime - LastUpdateSent; + // When connected to a service or not the server, always use the synchronized server time as opposed to the local time + var time = m_InternalNetworkManager.CMBServiceConnection || !m_InternalNetworkManager.IsServer ? m_NetworkBehaviour.NetworkManager.ServerTime.Time : m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime; + var timeSinceLastUpdate = time - LastUpdateSent; return ( UpdateTraits.MaxSecondsBetweenUpdates > 0 && @@ -201,7 +203,8 @@ internal bool CanSend() internal void UpdateLastSentTime() { - LastUpdateSent = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime; + // When connected to a service or not the server, always use the synchronized server time as opposed to the local time + LastUpdateSent = m_InternalNetworkManager.CMBServiceConnection || !m_InternalNetworkManager.IsServer ? m_NetworkBehaviour.NetworkManager.ServerTime.Time : m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime; } internal static bool IgnoreInitializeWarning; From 26c01ec6dc3b577cda9b97d933fc3e202e0d7aa3 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 9 Sep 2024 07:23:22 -0500 Subject: [PATCH 095/236] chore: example Non-Rigidbody CharacterController, moving bodies, smooth parenting transform space transitions (#3039) * chore Adding example of using a CharacterController that has no Rigidbody and being able to parent it under a moving and/or rotating body. This uses to be finalized NetworkTransform updates for NGO v2.x.x. * chore adding the manifest file * chore removing packages file * update Adding gitignore * fix Adding an elevator moving body. Fixing issue with disabling the GameObject vs disabling the CharacterController. * update Updating the example to mirror the most recent updates to #3013 * Create Readme.md place holder read me * Update Readme.md WIP updates added some additional sections and screenshots * update Adding [CanEditMultipleObjects] to RotatingBodyLogicEditor. Minor scene updates. * Update Readme.md * Update Readme.md * update cleaning up a bit. combining connect disconnect notifications into the extended networkmanager. made the single z-axis motion a path as opposed to the hacky way I did it originally. * update Adding some bumpers for extended networkmanager and selecting a connection type vs the selected network topology and vice versa. * Update Readme.md * update pointing to the develop-2.0.0 branch --- .../.gitignore | 83 + .../Assets/DefaultNetworkPrefabs.asset | 31 + .../Assets/DefaultNetworkPrefabs.asset.meta | 8 + .../Assets/Materials.meta | 8 + .../Assets/Materials/ChildLocal.mat | 86 + .../Assets/Materials/ChildLocal.mat.meta | 8 + .../Materials/ChildLocalNoParentSync.mat | 86 + .../Materials/ChildLocalNoParentSync.mat.meta | 8 + .../Assets/Materials/ChildWorld.mat | 86 + .../Assets/Materials/ChildWorld.mat.meta | 8 + .../Materials/ChildWorldNoParentSync.mat | 86 + .../Materials/ChildWorldNoParentSync.mat.meta | 8 + .../Assets/Materials/Direction.mat | 84 + .../Assets/Materials/Direction.mat.meta | 8 + .../Assets/Materials/GridBlue.mat | 138 ++ .../Assets/Materials/GridBlue.mat.meta | 8 + .../Assets/Materials/GridOrange.mat | 138 ++ .../Assets/Materials/GridOrange.mat.meta | 8 + .../Assets/Materials/GridPattern.mat | 85 + .../Assets/Materials/GridPattern.mat.meta | 8 + .../Assets/Materials/GridWhite.mat | 138 ++ .../Assets/Materials/GridWhite.mat.meta | 8 + .../Assets/Materials/Ground.mat | 79 + .../Assets/Materials/Ground.mat.meta | 8 + .../Assets/Materials/PlayerMaterial.mat | 80 + .../Assets/Materials/PlayerMaterial.mat.meta | 8 + .../Materials/ShaderGraphGrid_01_Mat.mat | 134 ++ .../Materials/ShaderGraphGrid_01_Mat.mat.meta | 8 + .../Assets/Models.meta | 8 + .../Assets/Models/Ramp_100x100x200_Mesh.fbx | Bin 0 -> 15712 bytes .../Models/Ramp_100x100x200_Mesh.fbx.meta | 107 ++ .../Assets/Models/Ramp_Mesh.fbx | Bin 0 -> 42320 bytes .../Assets/Models/Ramp_Mesh.fbx.meta | 107 ++ .../Assets/Models/Tunnel_Mesh.fbx | Bin 0 -> 45056 bytes .../Assets/Models/Tunnel_Mesh.fbx.meta | 107 ++ .../Assets/Models/Wall_Mesh.fbx | Bin 0 -> 45440 bytes .../Assets/Models/Wall_Mesh.fbx.meta | 107 ++ .../Assets/Prefabs.meta | 8 + .../Assets/Prefabs/Floor.physicMaterial | 15 + .../Assets/Prefabs/Floor.physicMaterial.meta | 8 + .../Assets/Prefabs/PlayerNoRigidbody.prefab | 960 +++++++++++ .../Prefabs/PlayerNoRigidbody.prefab.meta | 7 + .../Assets/Prefabs/Ramp_Prefab.prefab | 111 ++ .../Assets/Prefabs/Ramp_Prefab.prefab.meta | 7 + .../Prefabs/RotatingBody.physicMaterial | 15 + .../Prefabs/RotatingBody.physicMaterial.meta | 8 + .../Assets/Prefabs/RotatingBody.prefab | 1138 +++++++++++++ .../Assets/Prefabs/RotatingBody.prefab.meta | 7 + .../Assets/Prefabs/SceneLevelGeometry.prefab | 794 +++++++++ .../Prefabs/SceneLevelGeometry.prefab.meta | 7 + .../Assets/Prefabs/Tunnel_Prefab.prefab | 154 ++ .../Assets/Prefabs/Tunnel_Prefab.prefab.meta | 7 + .../Assets/Prefabs/Wall_Prefab.prefab | 132 ++ .../Assets/Prefabs/Wall_Prefab.prefab.meta | 7 + .../Assets/Resources.meta | 8 + .../Assets/Resources/BillingMode.json | 1 + .../Assets/Resources/BillingMode.json.meta | 7 + .../Assets/Scenes.meta | 8 + .../Assets/Scenes/Camera.preset | 195 +++ .../Assets/Scenes/Camera.preset.meta | 8 + .../Assets/Scenes/CharacterController.unity | 1434 +++++++++++++++++ .../Scenes/CharacterController.unity.meta | 7 + .../Assets/Scenes/SampleScenePrefabs.asset | 16 + .../Scenes/SampleScenePrefabs.asset.meta | 8 + .../Assets/Scripts.meta | 8 + .../Assets/Scripts/ExtendedNetworkManager.cs | 427 +++++ .../Scripts/ExtendedNetworkManager.cs.meta | 11 + .../Assets/Scripts/MoverScriptNoRigidbody.cs | 363 +++++ .../Scripts/MoverScriptNoRigidbody.cs.meta | 2 + .../Assets/Scripts/PlayerBallMotion.cs | 126 ++ .../Assets/Scripts/PlayerBallMotion.cs.meta | 2 + .../Assets/Scripts/PlayerColor.cs | 44 + .../Assets/Scripts/PlayerColor.cs.meta | 11 + .../Assets/Scripts/RotatingBodyLogic.cs | 201 +++ .../Assets/Scripts/RotatingBodyLogic.cs.meta | 2 + .../Assets/Scripts/ServerHostClientText.cs | 79 + .../Scripts/ServerHostClientText.cs.meta | 11 + .../Assets/Scripts/TriggerPush.cs | 63 + .../Assets/Scripts/TriggerPush.cs.meta | 2 + .../Assets/Textures.meta | 8 + .../Assets/Textures/GridPattern.png | Bin 0 -> 3152344 bytes .../Assets/Textures/GridPattern.png.meta | 153 ++ .../Assets/Textures/Grid_01_BaseMap.png | Bin 0 -> 8210 bytes .../Assets/Textures/Grid_01_BaseMap.png.meta | 130 ++ .../Assets/Textures/Grid_01_Emissive.png | Bin 0 -> 8225 bytes .../Assets/Textures/Grid_01_Emissive.png.meta | 130 ++ .../Assets/Textures/Grid_01_Normal.png | Bin 0 -> 21103 bytes .../Assets/Textures/Grid_01_Normal.png.meta | 130 ++ .../Assets/Textures/Grid_02_BaseMap.png | Bin 0 -> 9489 bytes .../Assets/Textures/Grid_02_BaseMap.png.meta | 130 ++ .../Packages/manifest.json | 55 + .../ProjectSettings/EditorBuildSettings.asset | 12 + .../ProjectSettings/ProjectSettings.asset | 687 ++++++++ .../CharacterControllerMovingBodies/Readme.md | 27 + 94 files changed, 9773 insertions(+) create mode 100644 Examples/CharacterControllerMovingBodies/.gitignore create mode 100644 Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset create mode 100644 Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_100x100x200_Mesh.fbx create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_100x100x200_Mesh.fbx.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_Mesh.fbx create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_Mesh.fbx.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Tunnel_Mesh.fbx create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Tunnel_Mesh.fbx.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Wall_Mesh.fbx create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Models/Wall_Mesh.fbx.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Resources.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scenes.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/GridPattern.png create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/GridPattern.png.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_BaseMap.png create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_BaseMap.png.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Emissive.png create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Emissive.png.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Normal.png create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Normal.png.meta create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_02_BaseMap.png create mode 100644 Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_02_BaseMap.png.meta create mode 100644 Examples/CharacterControllerMovingBodies/Packages/manifest.json create mode 100644 Examples/CharacterControllerMovingBodies/ProjectSettings/EditorBuildSettings.asset create mode 100644 Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectSettings.asset create mode 100644 Examples/CharacterControllerMovingBodies/Readme.md diff --git a/Examples/CharacterControllerMovingBodies/.gitignore b/Examples/CharacterControllerMovingBodies/.gitignore new file mode 100644 index 0000000000..2800399634 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/.gitignore @@ -0,0 +1,83 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore +# +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Asset meta data should only be ignored when the corresponding asset is also ignored +!/[Aa]ssets/**/*.meta + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.aab +*.unitypackage + +# Crashlytics generated file +crashlytics-build.properties + +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json.meta + +# Secrets +*.pem +*.pem.meta + +InitTestScene* + +boot.config +SceneTemplateSettings.json +*BurstAotSettings*.json diff --git a/Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset b/Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset new file mode 100644 index 0000000000..073f4484e8 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e651dbb3fbac04af2b8f5abf007ddc23, type: 3} + m_Name: DefaultNetworkPrefabs + m_EditorClassIdentifier: + IsDefault: 1 + List: + - Override: 0 + Prefab: {fileID: 2522762726852386808, guid: 380c984d34fc8664c8f53fc1d8733a25, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 8921789205124766477, guid: 89b57e576a8d47643b2dbd45b1f8cab1, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 3439633038736912633, guid: 398aad09d8b2a47eba664a076763cdcc, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} diff --git a/Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset.meta b/Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset.meta new file mode 100644 index 0000000000..fee27b3ade --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/DefaultNetworkPrefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aa82390bfdde2564f828b8e5be375282 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials.meta new file mode 100644 index 0000000000..463de70d61 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d5b7ad71451c27e4291295cfffc10328 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat new file mode 100644 index 0000000000..b6fd2dd9a1 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildLocal + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.8980392, g: 0.039215658, b: 0.7682729, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat.meta new file mode 100644 index 0000000000..35e4d565be --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocal.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45fc555bc05bfee4ab8b0d536799ecee +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat new file mode 100644 index 0000000000..c44172e7a7 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildLocalNoParentSync + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.039215658, g: 0.78592235, b: 0.8980392, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat.meta new file mode 100644 index 0000000000..3a26e43d98 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildLocalNoParentSync.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: affef70511a06dd46b8f52636020af4a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat new file mode 100644 index 0000000000..0c2bf4b187 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildWorld + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.3783494, g: 0.039215658, b: 0.8980392, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat.meta new file mode 100644 index 0000000000..9a00ded8c6 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorld.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15d0bda12a233964086aee5c0c357e24 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat new file mode 100644 index 0000000000..86b27eb5c9 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildWorldNoParentSync + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.039215658, g: 0.8980392, b: 0.09095798, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat.meta new file mode 100644 index 0000000000..5ab7ff2e72 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ChildWorldNoParentSync.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a5e199307b2e0894294d9c8bee99a691 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat new file mode 100644 index 0000000000..ed5ed117c3 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat @@ -0,0 +1,84 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Direction + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 0.8962264, g: 0.52830994, b: 0.038047332, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat.meta new file mode 100644 index 0000000000..c93791fac0 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/Direction.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5324c76c2bab7344badd5ea27a40bcb5 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat new file mode 100644 index 0000000000..9009817e26 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat @@ -0,0 +1,138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-2624353793879203111 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridBlue + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _EMISSION + - _GLOSSYREFLECTIONS_OFF + - _NORMALMAP + m_InvalidKeywords: [] + m_LightmapFlags: 2 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: b94463ba36040ec4082132c54dd1bbad, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 8bf2cf149563066489e749ea032dbca7, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: e71f43865e91e6b418bd0d67be2445dc, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 1 + - _Glossiness: 0.477 + - _GlossyReflections: 0 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _UVSec: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 0.5438323, g: 0.88474977, b: 0.9528302, a: 1} + - _EmissionColor: {r: 0, g: 0.6988592, b: 1, a: 1} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat.meta new file mode 100644 index 0000000000..b8caf3cbac --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridBlue.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1448a5da57523ce4bbc377143e02fe3c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat new file mode 100644 index 0000000000..38a4e9808b --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat @@ -0,0 +1,138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-2624353793879203111 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridOrange + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _EMISSION + - _GLOSSYREFLECTIONS_OFF + - _NORMALMAP + m_InvalidKeywords: [] + m_LightmapFlags: 2 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: b94463ba36040ec4082132c54dd1bbad, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 8bf2cf149563066489e749ea032dbca7, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: e71f43865e91e6b418bd0d67be2445dc, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 1 + - _Glossiness: 0.477 + - _GlossyReflections: 0 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _UVSec: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 1, g: 0.49475378, b: 0, a: 1} + - _EmissionColor: {r: 0.990566, g: 0.48359674, b: 0.378471, a: 1} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat.meta new file mode 100644 index 0000000000..bf2873069e --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridOrange.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2e2c886f4af8e304eb9a1e2e50d023b3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat new file mode 100644 index 0000000000..f44981f387 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat @@ -0,0 +1,85 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridPattern + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _GLOSSYREFLECTIONS_OFF + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 1 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 6, y: 6} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: a092c5fa8c60ed04aa1d72555f1740bc, type: 3} + m_Scale: {x: 6, y: 6} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0.785 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0.254717, g: 0.23188858, b: 0.23188858, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat.meta new file mode 100644 index 0000000000..cbca0f4485 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridPattern.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 42c4a0ad1f9d67a45b12f68697321aad +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat new file mode 100644 index 0000000000..7345253099 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat @@ -0,0 +1,138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-2624353793879203111 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridWhite + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _EMISSION + - _GLOSSYREFLECTIONS_OFF + - _NORMALMAP + m_InvalidKeywords: [] + m_LightmapFlags: 2 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: b94463ba36040ec4082132c54dd1bbad, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 8bf2cf149563066489e749ea032dbca7, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: e71f43865e91e6b418bd0d67be2445dc, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 1 + - _Glossiness: 0.477 + - _GlossyReflections: 0 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _UVSec: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 1, g: 1, b: 1, a: 1} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat.meta new file mode 100644 index 0000000000..4559482bba --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/GridWhite.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a53ba8919fa78c14caac473c7e7ce7d3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat new file mode 100644 index 0000000000..252ea1a0ed --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat @@ -0,0 +1,79 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Ground + m_Shader: {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + m_ShaderKeywords: + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 0.14769986, g: 0.1509434, b: 0.1473834, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat.meta new file mode 100644 index 0000000000..6dbcac16d3 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/Ground.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9c73b921ea39f4344a19c2d1c7d6b314 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat new file mode 100644 index 0000000000..e7f5729956 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat @@ -0,0 +1,80 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: PlayerMaterial + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat.meta new file mode 100644 index 0000000000..1ceca58536 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/PlayerMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 44e292334941fe148b997ca2b01b5789 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat b/Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat new file mode 100644 index 0000000000..5161ac9b8e --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat @@ -0,0 +1,134 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ShaderGraphGrid_01_Mat + m_Shader: {fileID: -6465566751694194690, guid: b8d7679189d4a5940af46004f3870920, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - Texture2D_C5E3E723: + m_Texture: {fileID: 2800000, guid: d4d6919451fe3e24388816386a6d15a4, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _Grid_Normal_Map: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: d9c0dd5cdac07b145be73329e489869a, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0.004, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - Boolean_35A71344: 0 + - Boolean_7A5F3F39: 1 + - Boolean_7AC8D832: 1 + - Vector1_3402D67A: 1 + - Vector1_3655428E: 5 + - Vector1_5B05FA1F: 0.062 + - Vector1_6B67A8FF: -20 + - Vector1_7810F718: 1 + - Vector1_B6126E6E: 0.335 + - Vector1_CA7D5F3: 30 + - Vector1_D5FBE925: 0.3 + - Vector1_F2A922B4: 1.73 + - Vector1_F5FD9210: 33.9 + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.119 + - _GlossyReflections: 1 + - _Grid_Normal_Strength: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueControl: 0 + - _QueueOffset: 0 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - Color_2B671050: {r: 0.23202202, g: 0.6245157, b: 0.745283, a: 0} + - Color_30A0CA2F: {r: 0.02745098, g: 1, b: 0.7565653, a: 0} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] +--- !u!114 &6450197988115792188 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta b/Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta new file mode 100644 index 0000000000..efd03db61f --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7be68f3cac640fd40a7663ac97380a9c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Models.meta b/Examples/CharacterControllerMovingBodies/Assets/Models.meta new file mode 100644 index 0000000000..21910d8d7a --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Models.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd50c879f39d9d94b92706513b4f56ef +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_100x100x200_Mesh.fbx b/Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_100x100x200_Mesh.fbx new file mode 100644 index 0000000000000000000000000000000000000000..2fa479b052cab2b81a3182ab6919d00774145096 GIT binary patch literal 15712 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 literal 0 HcmV?d00001 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_Mesh.fbx.meta b/Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_Mesh.fbx.meta new file mode 100644 index 0000000000..f9aabd0c03 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Models/Ramp_Mesh.fbx.meta @@ -0,0 +1,107 @@ +fileFormatVersion: 2 +guid: 5d8449220d5795b448a4cee5cbde4b56 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Models/Tunnel_Mesh.fbx b/Examples/CharacterControllerMovingBodies/Assets/Models/Tunnel_Mesh.fbx new file mode 100644 index 0000000000000000000000000000000000000000..7ec9d4bb74a7b846a5ad5f702842793d40fe839d GIT binary patch literal 45056 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 z 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 literal 0 HcmV?d00001 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Models/Wall_Mesh.fbx.meta b/Examples/CharacterControllerMovingBodies/Assets/Models/Wall_Mesh.fbx.meta new file mode 100644 index 0000000000..15452589e0 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Models/Wall_Mesh.fbx.meta @@ -0,0 +1,107 @@ +fileFormatVersion: 2 +guid: 34560503bf9d61046b252db98a8cf770 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs.meta new file mode 100644 index 0000000000..c867cd3a70 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4b97118ef11ec3347bc72b8d681e094b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial new file mode 100644 index 0000000000..5aeefdde15 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!134 &13400000 +PhysicsMaterial: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Floor + serializedVersion: 2 + m_DynamicFriction: 2.5 + m_StaticFriction: 2 + m_Bounciness: 0 + m_FrictionCombine: 3 + m_BounceCombine: 2 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial.meta new file mode 100644 index 0000000000..a55550d94a --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Floor.physicMaterial.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c574f6ade946d94f9ec0183e3bc4579 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 13400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab new file mode 100644 index 0000000000..ca1d2b414e --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab @@ -0,0 +1,960 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2819221948576051598 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8815849029736207364} + - component: {fileID: 3042900316038666559} + - component: {fileID: 1437858011576020080} + - component: {fileID: 8264281895793262867} + m_Layer: 0 + m_Name: PlayerBallChild1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8815849029736207364 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2819221948576051598} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 1, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 394823601370723229} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &3042900316038666559 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2819221948576051598} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &1437858011576020080 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2819221948576051598} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!114 &8264281895793262867 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2819221948576051598} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 1 + ExpandNetworkTransform: 1 + RotationAxis: 2 + RotationSpeed: 1.5 +--- !u!1 &3959781627078922459 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 394823601370723229} + - component: {fileID: 1530657457870194733} + - component: {fileID: 8179170832269307327} + - component: {fileID: 8231288296501785146} + m_Layer: 0 + m_Name: PlayerBallPrime + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &394823601370723229 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3959781627078922459} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.25, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8815849029736207364} + - {fileID: 7136463849438099188} + - {fileID: 5551906405844277949} + m_Father: {fileID: 8921789205124766473} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &1530657457870194733 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3959781627078922459} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &8179170832269307327 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3959781627078922459} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a53ba8919fa78c14caac473c7e7ce7d3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!114 &8231288296501785146 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3959781627078922459} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 1 + ExpandNetworkTransform: 1 + RotationAxis: 0 + RotationSpeed: 1.5 +--- !u!1 &3973637191948275635 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8797190989880117053} + - component: {fileID: 1956256172504790623} + - component: {fileID: 2171905566637076429} + m_Layer: 0 + m_Name: ParentedText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8797190989880117053 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3973637191948275635} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1.91, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8921789205124766473} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1956256172504790623 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3973637191948275635} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!102 &2171905566637076429 +TextMesh: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3973637191948275635} + m_Text: "Hello \nWorld" + m_OffsetZ: 0 + m_CharacterSize: 0.15 + m_LineSpacing: 1 + m_Anchor: 4 + m_Alignment: 1 + m_TabSize: 4 + m_FontSize: 20 + m_FontStyle: 1 + m_RichText: 1 + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_Color: + serializedVersion: 2 + rgba: 4294967295 +--- !u!1 &7133739606324490315 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5551906405844277949} + - component: {fileID: 1792842414970541714} + - component: {fileID: 6744917009213370100} + - component: {fileID: 6571689152541036907} + m_Layer: 0 + m_Name: PlayerBallChild3 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5551906405844277949 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7133739606324490315} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 1} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 394823601370723229} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &1792842414970541714 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7133739606324490315} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &6744917009213370100 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7133739606324490315} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!114 &6571689152541036907 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7133739606324490315} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 1 + ExpandNetworkTransform: 1 + RotationAxis: 1 + RotationSpeed: 1.5 +--- !u!1 &7484009658662050968 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7136463849438099188} + - component: {fileID: 8767029715903736994} + - component: {fileID: 186997181429634371} + - component: {fileID: 4424954456620528769} + m_Layer: 0 + m_Name: PlayerBallChild2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7136463849438099188 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7484009658662050968} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -1, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 394823601370723229} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &8767029715903736994 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7484009658662050968} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &186997181429634371 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7484009658662050968} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!114 &4424954456620528769 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7484009658662050968} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 0 + ExpandNetworkTransform: 1 + RotationAxis: 0 + RotationSpeed: 1.5 +--- !u!1 &8837707216906300506 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3256164543801378073} + - component: {fileID: 5138046720836505354} + - component: {fileID: 1048758437790369019} + - component: {fileID: 3232232006964461868} + m_Layer: 0 + m_Name: Direction + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3256164543801378073 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8837707216906300506} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.529, z: 0.36} + m_LocalScale: {x: 0.5, y: 0.35, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8921789205124766473} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &5138046720836505354 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8837707216906300506} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &1048758437790369019 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8837707216906300506} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 5324c76c2bab7344badd5ea27a40bcb5, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &3232232006964461868 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8837707216906300506} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 0 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &8921789205124766477 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8921789205124766473} + - component: {fileID: 8921789205124766472} + - component: {fileID: 8921789205124766479} + - component: {fileID: 8921789205124766478} + - component: {fileID: 3121348088455848731} + - component: {fileID: 8921789205124766474} + - component: {fileID: 871737567343884637} + - component: {fileID: 571224925323069553} + - component: {fileID: 2509722539211228765} + m_Layer: 0 + m_Name: PlayerNoRigidbody + m_TagString: Player + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8921789205124766473 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3256164543801378073} + - {fileID: 8797190989880117053} + - {fileID: 394823601370723229} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &8921789205124766472 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &8921789205124766479 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 44e292334941fe148b997ca2b01b5789, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!136 &8921789205124766478 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Height: 2 + m_Direction: 1 + m_Center: {x: 0, y: 0, z: 0} +--- !u!143 &3121348088455848731 +CharacterController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Height: 2 + m_Radius: 0.5 + m_SlopeLimit: 45 + m_StepOffset: 0.3 + m_SkinWidth: 0.08 + m_MinMoveDistance: 0.001 + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &8921789205124766474 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2508530451 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 0 + AllowOwnerToParent: 1 +--- !u!114 &871737567343884637 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8e5128237997be649af0cc87dd0eb563, type: 3} + m_Name: + m_EditorClassIdentifier: + ApplyColorToChildren: 1 + IgnoreChildren: + - {fileID: 8837707216906300506} +--- !u!114 &571224925323069553 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5ce25b0b3f15e6446a88a85787c2f94a, type: 3} + m_Name: + m_EditorClassIdentifier: + AuthorityMode: 1 + TickSyncChildren: 1 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.01 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.1 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 0 + SwitchTransformSpaceWhenParented: 1 + Interpolate: 1 + SlerpPosition: 0 + ExpandMoverScriptNoRigidbody: 0 + ExpandNetworkTransform: 1 + SpawnRadius: 10 + Increment: 1 + RotationSpeed: 1.26 + MovementSpeed: 15 + JumpSpeed: 10 + AirSpeedFactor: 0.35 + Gravity: -9.8 +--- !u!65 &2509722539211228765 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8921789205124766477} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1.4, z: 1} + m_Center: {x: 0, y: 0, z: 0} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab.meta new file mode 100644 index 0000000000..9199f72759 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/PlayerNoRigidbody.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8ae02ac62e2067144b8ff06d48aeb47a +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab new file mode 100644 index 0000000000..edcdf3071c --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab @@ -0,0 +1,111 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &8924170145835402666 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8111781018561290000} + - component: {fileID: 5133274882688487605} + - component: {fileID: 6978882906433643647} + - component: {fileID: 894093325933845257} + m_Layer: 0 + m_Name: Ramp_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &8111781018561290000 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + serializedVersion: 2 + m_LocalRotation: {x: 0.000000021855694, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &5133274882688487605 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!23 &6978882906433643647 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!64 &894093325933845257 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 5 + m_Convex: 0 + m_CookingOptions: 30 + m_Mesh: {fileID: -8512782951310809723, guid: 426a2785f8a940049aac2c246661cf09, type: 3} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab.meta new file mode 100644 index 0000000000..14e8009f6b --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Ramp_Prefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7ab2ae375810b5641a36d327b9f022cf +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial new file mode 100644 index 0000000000..f76e8e1f5a --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!134 &13400000 +PhysicsMaterial: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: RotatingBody + serializedVersion: 2 + m_DynamicFriction: 2.5 + m_StaticFriction: 2 + m_Bounciness: 0 + m_FrictionCombine: 3 + m_BounceCombine: 2 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial.meta new file mode 100644 index 0000000000..4650ebf5b0 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.physicMaterial.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c16e8d98094923449892b28a230ddb9c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 13400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab new file mode 100644 index 0000000000..73c04c02fa --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab @@ -0,0 +1,1138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &755183729697733696 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 522841341294848418} + - component: {fileID: 6376943934387906635} + - component: {fileID: 4793038284696412839} + m_Layer: 0 + m_Name: WallPusherA_Inner + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &522841341294848418 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 755183729697733696} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 4.05, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &6376943934387906635 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 755183729697733696} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &4793038284696412839 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 755183729697733696} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 1 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} +--- !u!1 &1070469363212057228 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4967607898511807569} + - component: {fileID: 4081511419635538304} + - component: {fileID: 3264090684553753291} + - component: {fileID: 2074962368930179783} + - component: {fileID: 2659845886479429441} + - component: {fileID: 365707591732078506} + m_Layer: 0 + m_Name: Tunnel_Prefab (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &4967607898511807569 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + serializedVersion: 2 + m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 5, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} +--- !u!33 &4081511419635538304 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Mesh: {fileID: 5060444177187149915, guid: 63fcabcd345d556498e09f748683088e, type: 3} +--- !u!23 &3264090684553753291 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &2074962368930179783 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Material: {fileID: 13400000, guid: c16e8d98094923449892b28a230ddb9c, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2.502674, y: 6.0000024, z: 0.25225586} + m_Center: {x: 0, y: -5.4553292e-15, z: 2.3838842} +--- !u!65 &2659845886479429441 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.19382703, y: 6.0000024, z: 2.2399507} + m_Center: {x: -1.1314733, y: -1.7786642e-15, z: 1.1251621} +--- !u!65 &365707591732078506 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.16720939, y: 6.0000024, z: 2.256102} + m_Center: {x: 1.1123942, y: -1.893427e-15, z: 1.1332378} +--- !u!1 &1227036625448805321 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6179387521291227856} + - component: {fileID: 6043205804484853104} + - component: {fileID: 7583159525709273484} + - component: {fileID: 8095289646395249164} + m_Layer: 0 + m_Name: Ramp_PrefabA + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &6179387521291227856 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + serializedVersion: 2 + m_LocalRotation: {x: 0.000000021855694, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.25, z: 7.993} + m_LocalScale: {x: 8, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3394725634534525932} + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &6043205804484853104 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!23 &7583159525709273484 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!64 &8095289646395249164 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + m_Material: {fileID: 13400000, guid: c16e8d98094923449892b28a230ddb9c, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 5 + m_Convex: 1 + m_CookingOptions: 30 + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!1 &1451894099667441545 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3394725634534525932} + - component: {fileID: 2385627259374300527} + m_Layer: 0 + m_Name: BridgeGap + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3394725634534525932 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1451894099667441545} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: 1, z: -0.000000021855694, w: 0} + m_LocalPosition: {x: 0, y: 0.14, z: -1.0100002} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6179387521291227856} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!136 &2385627259374300527 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1451894099667441545} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.1 + m_Height: 1.15 + m_Direction: 0 + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &2406660182425334495 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1877966211327071347} + - component: {fileID: 3334163744623252575} + - component: {fileID: 5776191766534439988} + - component: {fileID: 6535282611511474253} + - component: {fileID: 8963258823839906869} + - component: {fileID: 7304575238635320140} + m_Layer: 0 + m_Name: Tunnel_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &1877966211327071347 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + serializedVersion: 2 + m_LocalRotation: {x: -0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 4.96, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: -90, y: 0, z: 0} +--- !u!33 &3334163744623252575 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Mesh: {fileID: 5060444177187149915, guid: 63fcabcd345d556498e09f748683088e, type: 3} +--- !u!23 &5776191766534439988 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &6535282611511474253 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2.502674, y: 6.0000024, z: 0.258326} + m_Center: {x: 0, y: 5.4121983e-15, z: 2.3808491} +--- !u!65 &8963258823839906869 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2527809, y: 6.0000024, z: 2.2399507} + m_Center: {x: -1.1249466, y: 1.7786642e-15, z: 1.1251621} +--- !u!65 &7304575238635320140 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.26525307, y: 6.0000024, z: 2.256102} + m_Center: {x: 1.1187105, y: 1.893427e-15, z: 1.1332378} +--- !u!1 &2492174073242389836 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6182591213277846585} + - component: {fileID: 3395949877673187758} + m_Layer: 0 + m_Name: BridgeGap (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6182591213277846585 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2492174073242389836} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -1, z: 0.000000021855694, w: 0} + m_LocalPosition: {x: 0, y: 0.14, z: -1.01} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7826839884382864090} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!136 &3395949877673187758 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2492174073242389836} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.1 + m_Height: 1.15 + m_Direction: 0 + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &3038314618705458857 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3570252208687936609} + - component: {fileID: 4156819582579138816} + - component: {fileID: 2806575150631930745} + m_Layer: 0 + m_Name: WallPusherB_Inner + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &3570252208687936609 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3038314618705458857} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -4.07, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &4156819582579138816 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3038314618705458857} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &2806575150631930745 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3038314618705458857} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 0 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} +--- !u!1 &4405129256840456534 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5628673131512617452} + - component: {fileID: 1065561891622964567} + - component: {fileID: 4468158207537748033} + m_Layer: 0 + m_Name: WallPusherA_Outer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &5628673131512617452 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4405129256840456534} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 4.89, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &1065561891622964567 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4405129256840456534} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &4468158207537748033 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4405129256840456534} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 0 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} +--- !u!1 &5415449980466536476 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4291553105548296809} + - component: {fileID: 8134939553748259768} + - component: {fileID: 3307166493715739449} + - component: {fileID: 850162744905636139} + m_Layer: 0 + m_Name: RotatingBody + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4291553105548296809 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1877966211327071347} + - {fileID: 4967607898511807569} + - {fileID: 522841341294848418} + - {fileID: 5628673131512617452} + - {fileID: 3570252208687936609} + - {fileID: 5389614242607036533} + - {fileID: 6179387521291227856} + - {fileID: 7826839884382864090} + - {fileID: 2053800158975384476} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &8134939553748259768 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 8.023605, y: 8.144613, z: 20.106205} + m_Center: {x: -0.03800702, y: 4.1726913, z: -0.02903366} +--- !u!114 &3307166493715739449 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1921503253 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 2 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 1 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 +--- !u!114 &850162744905636139 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 374ac199eb18f0f468bc018a722775c2, type: 3} + m_Name: + m_EditorClassIdentifier: + AuthorityMode: 1 + TickSyncChildren: 1 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.1 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.1 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 0 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 0 + RotationSpeed: 0.25 + RotateDirection: 0 + ZAxisMove: 0 +--- !u!1 &5463907175177238004 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2053800158975384476} + - component: {fileID: 2688281628964240045} + m_Layer: 0 + m_Name: Point Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2053800158975384476 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5463907175177238004} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 6.54, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!108 &2688281628964240045 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5463907175177238004} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 2 + m_Color: {r: 0.9622642, g: 0.9622642, b: 0.9622642, a: 1} + m_Intensity: 2 + m_Range: 20 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 0 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!1 &6529740436184164063 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7826839884382864090} + - component: {fileID: 2797882924116045225} + - component: {fileID: 1097787484049525257} + - component: {fileID: 570708159460268413} + m_Layer: 0 + m_Name: Ramp_PrefabB + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &7826839884382864090 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 1, z: 0, w: 0} + m_LocalPosition: {x: 0, y: 0.25, z: -7.993} + m_LocalScale: {x: 8, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6182591213277846585} + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 180, z: 0} +--- !u!33 &2797882924116045225 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!23 &1097787484049525257 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!64 &570708159460268413 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + m_Material: {fileID: 13400000, guid: c16e8d98094923449892b28a230ddb9c, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 5 + m_Convex: 1 + m_CookingOptions: 30 + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!1 &8002386465640125644 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5389614242607036533} + - component: {fileID: 4101224782146625552} + - component: {fileID: 6895917012766152111} + m_Layer: 0 + m_Name: WallPusherB_Outer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &5389614242607036533 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8002386465640125644} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -4.83, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &4101224782146625552 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8002386465640125644} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &6895917012766152111 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8002386465640125644} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 1 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab.meta new file mode 100644 index 0000000000..d22d47036e --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/RotatingBody.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0123d7125346c274da00b38e950a266b +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab new file mode 100644 index 0000000000..f636c60a54 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab @@ -0,0 +1,794 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &910007655143077103 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6283120762215196916} + - component: {fileID: 3739510624437302406} + m_Layer: 0 + m_Name: CornerBumper (1) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6283120762215196916 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007655143077103} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0.9244967, z: -0, w: -0.38119} + m_LocalPosition: {x: -29.72, y: 0.98, z: 29.82} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -224.815, z: 0} +--- !u!65 &3739510624437302406 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007655143077103} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &1854705290947220173 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2290144462706082272} + - component: {fileID: 4559046433245738380} + m_Layer: 0 + m_Name: CornerBumper (2) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2290144462706082272 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705290947220173} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0.3771283, z: -0, w: -0.92616105} + m_LocalPosition: {x: -29.53, y: 0.98, z: -29.71} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -315.688, z: 0} +--- !u!65 &4559046433245738380 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705290947220173} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &4012615691354089848 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691354089844} + - component: {fileID: 4012615691354089845} + - component: {fileID: 4012615691354089850} + - component: {fileID: 4012615691354089851} + m_Layer: 0 + m_Name: Floor + m_TagString: Floor + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615691354089844 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: -0.50000006, z: 0} + m_LocalScale: {x: 60, y: 1, z: 60} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691354089845 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691354089850 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 42c4a0ad1f9d67a45b12f68697321aad, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615691354089851 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + m_Material: {fileID: 13400000, guid: 2c574f6ade946d94f9ec0183e3bc4579, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &4012615691503252843 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691503252839} + - component: {fileID: 4012615691503252836} + - component: {fileID: 4012615691503252837} + - component: {fileID: 4012615691503252842} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615691503252839 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691503252836 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691503252837 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615691503252842 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: -0.5, y: 0, z: 0} +--- !u!1 &4012615691965054905 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691965054901} + - component: {fileID: 4012615691965054906} + - component: {fileID: 4012615691965054907} + - component: {fileID: 4012615691965054904} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615691965054901 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: 30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691965054906 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691965054907 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615691965054904 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: 0.5} +--- !u!1 &4012615692269653858 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692269653854} + - component: {fileID: 4012615692269653855} + - component: {fileID: 4012615692269653852} + - component: {fileID: 4012615692269653853} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615692269653854 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: -30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615692269653855 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615692269653852 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615692269653853 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: -0.5} +--- !u!1 &4012615692778511854 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692778511849} + m_Layer: 0 + m_Name: SceneLevelGeometry + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615692778511849 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692778511854} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.000000059604645, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4012615691354089844} + - {fileID: 4012615691965054901} + - {fileID: 4012615692269653854} + - {fileID: 4012615691503252839} + - {fileID: 4012615692791378778} + - {fileID: 3910294717376836327} + - {fileID: 6283120762215196916} + - {fileID: 2290144462706082272} + - {fileID: 6959258897999621209} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &4012615692791378782 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692791378778} + - component: {fileID: 4012615692791378779} + - component: {fileID: 4012615692791378776} + - component: {fileID: 4012615692791378777} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615692791378778 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615692791378779 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615692791378776 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615692791378777 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: 0.5, y: 0, z: 0} +--- !u!1 &4674276234353933548 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3910294717376836327} + - component: {fileID: 3136259738973340924} + m_Layer: 0 + m_Name: CornerBumper + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3910294717376836327 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276234353933548} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: -0.38268343, z: 0, w: 0.92387956} + m_LocalPosition: {x: 29.7, y: 0.98, z: -29.61} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -45, z: 0} +--- !u!65 &3136259738973340924 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276234353933548} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &7080625901286762351 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6959258897999621209} + - component: {fileID: 7672408768716900064} + m_Layer: 0 + m_Name: CornerBumper (3) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6959258897999621209 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625901286762351} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: 0.93588465, z: -0, w: -0.35230666} + m_LocalPosition: {x: 29.26, y: 0.98, z: 29.45} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -498.74298, z: 0} +--- !u!65 &7672408768716900064 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625901286762351} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab.meta new file mode 100644 index 0000000000..154fd718e7 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/SceneLevelGeometry.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3ec484313a7a6754dac871e620df8db2 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab new file mode 100644 index 0000000000..c596d7333d --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab @@ -0,0 +1,154 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1604908963751126680 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2093637950428003362} + - component: {fileID: 3071950872142852999} + - component: {fileID: 956227959320364877} + - component: {fileID: 4985949235297978144} + - component: {fileID: 8456313914433245678} + - component: {fileID: 1138964657491743937} + m_Layer: 0 + m_Name: Tunnel_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &2093637950428003362 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + serializedVersion: 2 + m_LocalRotation: {x: -0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: -90.00001, y: 0, z: 0} +--- !u!33 &3071950872142852999 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Mesh: {fileID: 5060444177187149915, guid: 63fcabcd345d556498e09f748683088e, type: 3} +--- !u!23 &956227959320364877 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4985949235297978144 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2.502674, y: 6.0000024, z: 0.258326} + m_Center: {x: 0, y: 5.4121983e-15, z: 2.3808491} +--- !u!65 &8456313914433245678 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2527809, y: 6.0000024, z: 2.2399507} + m_Center: {x: -1.1249466, y: 1.7786642e-15, z: 1.1251621} +--- !u!65 &1138964657491743937 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.26525307, y: 6.0000024, z: 2.256102} + m_Center: {x: 1.1187105, y: 1.893427e-15, z: 1.1332378} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab.meta new file mode 100644 index 0000000000..58def4a902 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Tunnel_Prefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4b50ff3d475fc3f4fa77ac6aa6e679f2 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab new file mode 100644 index 0000000000..f8dde4effc --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab @@ -0,0 +1,132 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &7993119983977949264 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7324705577624711914} + - component: {fileID: 5911343394670230863} + - component: {fileID: 8497650616581704069} + - component: {fileID: 9105854698657379725} + - component: {fileID: 1277700310800588604} + m_Layer: 0 + m_Name: Wall_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &7324705577624711914 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + serializedVersion: 2 + m_LocalRotation: {x: 0.000000021855694, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &5911343394670230863 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Mesh: {fileID: 6352809919239313146, guid: 34560503bf9d61046b252db98a8cf770, type: 3} +--- !u!23 &8497650616581704069 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &9105854698657379725 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.25, y: 1.0095696, z: 2.9958286} + m_Center: {x: 0, y: 0.5047848, z: 1.5020857} +--- !u!65 &1277700310800588604 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.25, y: 2.0055175, z: 3.000146} + m_Center: {x: 0, y: 1.0027587, z: -1.499927} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab.meta b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab.meta new file mode 100644 index 0000000000..42ef5284f2 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Prefabs/Wall_Prefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3b9516be83427084ca3fffca42e7b6da +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Resources.meta b/Examples/CharacterControllerMovingBodies/Assets/Resources.meta new file mode 100644 index 0000000000..edebf21a13 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6a51a9fbd254e544eb3e85853865f80d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json b/Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json new file mode 100644 index 0000000000..6f4bfb7103 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json @@ -0,0 +1 @@ +{"androidStore":"GooglePlay"} \ No newline at end of file diff --git a/Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json.meta b/Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json.meta new file mode 100644 index 0000000000..557e7d707c --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Resources/BillingMode.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a1890189503409a4bb24dd4f0eab1f0a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scenes.meta b/Examples/CharacterControllerMovingBodies/Assets/Scenes.meta new file mode 100644 index 0000000000..b398c5b4ab --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9bb955f9d9ef9c34d897f353c8643a1d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset b/Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset new file mode 100644 index 0000000000..b8bbae0a72 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset @@ -0,0 +1,195 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!181963792 &2655988077585873504 +Preset: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Camera + m_TargetType: + m_NativeTypeID: 20 + m_ManagedTypePPtr: {fileID: 0} + m_ManagedTypeFallback: + m_Properties: + - target: {fileID: 0} + propertyPath: m_Enabled + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_ClearFlags + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.r + value: 0.066037714 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.g + value: 0.066037714 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.b + value: 0.066037714 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.a + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_projectionMatrixMode + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_GateFitMode + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_FOVAxisMode + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Iso + value: 200 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_ShutterSpeed + value: 0.005 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Aperture + value: 16 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_FocusDistance + value: 10 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_FocalLength + value: 50 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BladeCount + value: 5 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Curvature.x + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Curvature.y + value: 11 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BarrelClipping + value: 0.25 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Anamorphism + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_SensorSize.x + value: 36 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_SensorSize.y + value: 24 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_LensShift.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_LensShift.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.width + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.height + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: near clip plane + value: 0.3 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: far clip plane + value: 1000 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: field of view + value: 60 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: orthographic + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: orthographic size + value: 5 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Depth + value: -1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_CullingMask.m_Bits + value: 4294967295 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_RenderingPath + value: -1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_TargetTexture + value: + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_TargetDisplay + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_TargetEye + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_HDR + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_AllowMSAA + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_AllowDynamicResolution + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_ForceIntoRT + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_OcclusionCulling + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_StereoConvergence + value: 10 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_StereoSeparation + value: 0.022 + objectReference: {fileID: 0} + m_ExcludedProperties: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset.meta b/Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset.meta new file mode 100644 index 0000000000..3e327ecf36 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scenes/Camera.preset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1807b530602915743868e6c3bdc1a93c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2655988077585873504 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity b/Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity new file mode 100644 index 0000000000..cca8cabbeb --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity @@ -0,0 +1,1434 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &28232985 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 28232986} + - component: {fileID: 28232988} + - component: {fileID: 28232987} + - component: {fileID: 28232989} + - component: {fileID: 28232990} + m_Layer: 5 + m_Name: ServerHostClientDisplay + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &28232986 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 479361665} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: 0, y: 40} + m_SizeDelta: {x: 180, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &28232987 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 23 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: +--- !u!222 &28232988 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_CullTransparentMesh: 1 +--- !u!114 &28232989 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 536662705 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!114 &28232990 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6637cd674efb56a48a3d4d545d23a8d3, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 +--- !u!1001 &45185844 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: OnExitTransferParentOnStay + value: + objectReference: {fileID: 621748559} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: DontDeparentIfParentedByOtherBody + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 3246499739 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: -60 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: -49.1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: StationaryBody-B + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Size.z + value: 19.891119 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.x + value: -0.038006783 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.z + value: -0.13657665 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1001 &66674670 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: OnExitTransferParentOnStay + value: + objectReference: {fileID: 520394643} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: DontDeparentIfParentedByOtherBody + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 4013775021 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: -60 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: -33.32 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: StationaryBody-A + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Size.z + value: 19.906477 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.x + value: -0.038006783 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.z + value: 0.070830345 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1 &455857869 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 455857870} + - component: {fileID: 455857875} + - component: {fileID: 455857871} + m_Layer: 0 + m_Name: ExtendedNetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &455857870 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &455857871 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 0 + m_UseWebSockets: 0 + m_UseEncryption: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 6144 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + ServerListenAddress: 127.0.0.1 + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 +--- !u!114 &455857875 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e4d8d44c602b97b47ba488a40c66267c, type: 3} + m_Name: + m_EditorClassIdentifier: + NetworkManagerExpanded: 0 + NetworkConfig: + ProtocolVersion: 0 + NetworkTransport: {fileID: 455857871} + PlayerPrefab: {fileID: 8921789205124766477, guid: 8ae02ac62e2067144b8ff06d48aeb47a, type: 3} + Prefabs: + NetworkPrefabsLists: + - {fileID: 11400000, guid: aa82390bfdde2564f828b8e5be375282, type: 2} + TickRate: 30 + ClientConnectionBufferTimeout: 10 + ConnectionApproval: 0 + ConnectionData: + EnableTimeResync: 0 + TimeResyncInterval: 30 + EnsureNetworkVariableLengthSafety: 0 + EnableSceneManagement: 1 + ForceSamePrefabs: 1 + RecycleNetworkIds: 1 + NetworkIdRecycleDelay: 120 + RpcHashSize: 0 + LoadSceneTimeOut: 120 + SpawnTimeout: 10 + EnableNetworkLogs: 1 + NetworkTopology: 0 + UseCMBService: 0 + AutoSpawnPlayerPrefabClientSide: 1 + NetworkProfilingMetrics: 1 + OldPrefabList: [] + RunInBackground: 1 + LogLevel: 1 + ExtendedNetworkManagerExpanded: 1 + ConnectionType: 1 + TargetFrameRate: 100 + EnableVSync: 0 + m_OriginalVSyncCount: 1 +--- !u!1 &479361661 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 479361665} + - component: {fileID: 479361664} + - component: {fileID: 479361663} + - component: {fileID: 479361662} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &479361662 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &479361663 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &479361664 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &479361665 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 28232986} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!114 &520394643 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + m_PrefabInstance: {fileID: 45185844} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 374ac199eb18f0f468bc018a722775c2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &537610708 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 537610709} + m_Layer: 0 + m_Name: PointA + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &537610709 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 537610708} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: -0.34, z: 70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &621748559 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + m_PrefabInstance: {fileID: 66674670} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 374ac199eb18f0f468bc018a722775c2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1001 &748186899 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalScale.x + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalScale.z + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.y + value: -0.25 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511854, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_Name + value: SceneLevelGeometry + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} +--- !u!1001 &857399335 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0.25 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotateDirection + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 1449196534 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: -60.7 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: 56.8 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: RotatingBody + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1 &946793187 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 946793188} + m_Layer: 0 + m_Name: PointE + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &946793188 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 946793187} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 60.5, y: -0.34, z: 70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.066037714, g: 0.066037714, b: 0.066037714, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + serializedVersion: 2 + m_LocalRotation: {x: 0.15212336, y: 0, z: 0, w: 0.98836154} + m_LocalPosition: {x: 0, y: 4.5, z: -5.5} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 17.5, y: 0, z: 0} +--- !u!1 &1049334975 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1049334976} + m_Layer: 0 + m_Name: PointF + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1049334976 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1049334975} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 60.5, y: -0.34, z: -68.5} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1055951929 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1055951930} + m_Layer: 0 + m_Name: PointD + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1055951930 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1055951929} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: -0.34, z: -70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1184702125 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1184702128} + - component: {fileID: 1184702127} + - component: {fileID: 1184702126} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1184702126 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184702125} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1184702127 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184702125} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1184702128 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184702125} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1281267714 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1281267715} + m_Layer: 0 + m_Name: PointB + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1281267715 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1281267714} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: 40.6, z: 70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &1591298748 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: ZAxisMove + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: PathMovement.Array.size + value: 4 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[0]' + value: + objectReference: {fileID: 537610708} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[1]' + value: + objectReference: {fileID: 1281267714} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[2]' + value: + objectReference: {fileID: 1727671589} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[3]' + value: + objectReference: {fileID: 1055951929} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 4258258070 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: 10.9 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: 70 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: 'ElevatorBody ' + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1 &1727671589 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1727671590} + m_Layer: 0 + m_Name: PointC + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1727671590 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1727671589} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: 40.6, z: -70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &1968567121 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: ZAxisMove + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0.3 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: PathMovement.Array.size + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotatingBodyLogicExpanded + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[0]' + value: + objectReference: {fileID: 946793187} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[1]' + value: + objectReference: {fileID: 1049334975} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 1259759422 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: 60 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: 70 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: MovingRotatingBody + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 963194228} + - {fileID: 705507995} + - {fileID: 455857870} + - {fileID: 479361665} + - {fileID: 1184702128} + - {fileID: 748186899} + - {fileID: 857399335} + - {fileID: 66674670} + - {fileID: 45185844} + - {fileID: 1968567121} + - {fileID: 1591298748} + - {fileID: 537610709} + - {fileID: 1281267715} + - {fileID: 1727671590} + - {fileID: 1055951930} + - {fileID: 946793188} + - {fileID: 1049334976} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity.meta b/Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity.meta new file mode 100644 index 0000000000..952bd1e9e1 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scenes/CharacterController.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9fc0d4010bbf28b4594072e72b8655ab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset b/Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset new file mode 100644 index 0000000000..26e47a6487 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e651dbb3fbac04af2b8f5abf007ddc23, type: 3} + m_Name: SampleScenePrefabs + m_EditorClassIdentifier: + IsDefault: 0 + List: [] diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset.meta b/Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset.meta new file mode 100644 index 0000000000..402e4425d7 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scenes/SampleScenePrefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3d25a2b1f6c12ee47bf7601c2edd7e70 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts.meta new file mode 100644 index 0000000000..528b6db2ae --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f0135d923712c4438b2facb3ce21fb6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs new file mode 100644 index 0000000000..2c291bbe89 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs @@ -0,0 +1,427 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Unity.Netcode; +using Unity.Services.Authentication; +using Unity.Services.Core; +using Unity.Services.Multiplayer; +using UnityEngine; +using SessionState = Unity.Services.Multiplayer.SessionState; + +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +///

+/// The custom editor for the component. +/// +[CustomEditor(typeof(ExtendedNetworkManager), true)] +[CanEditMultipleObjects] +public class ExtendedNetworkManagerEditor : NetworkManagerEditor +{ + private SerializedProperty m_ConnectionType; + private SerializedProperty m_TargetFrameRate; + private SerializedProperty m_EnableVSync; + + public override void OnEnable() + { + m_ConnectionType = serializedObject.FindProperty(nameof(ExtendedNetworkManager.ConnectionType)); + m_TargetFrameRate = serializedObject.FindProperty(nameof(ExtendedNetworkManager.TargetFrameRate)); + m_EnableVSync = serializedObject.FindProperty(nameof(ExtendedNetworkManager.EnableVSync)); + base.OnEnable(); + } + + private void DisplayExtendedNetworkManagerProperties() + { + EditorGUILayout.PropertyField(m_ConnectionType); + EditorGUILayout.PropertyField(m_TargetFrameRate); + EditorGUILayout.PropertyField(m_EnableVSync); + } + + public override void OnInspectorGUI() + { + var extendedNetworkManager = target as ExtendedNetworkManager; + // Handle switching the appropriate connection type based on the network topology + // Host connectio type can be set for client-server and distributed authority + // Live Service can only be used with distributed authority + // Client-server can only be used with a host connection type + var connectionTypes = Enum.GetValues(typeof(ExtendedNetworkManager.ConnectionTypes)); + var connectionType = ExtendedNetworkManager.ConnectionTypes.LiveService; + if (m_ConnectionType.enumValueIndex > 0 && m_ConnectionType.enumValueIndex < connectionTypes.Length) + { + connectionType = (ExtendedNetworkManager.ConnectionTypes)connectionTypes.GetValue(m_ConnectionType.enumValueIndex); + } + void SetExpanded(bool expanded) { extendedNetworkManager.ExtendedNetworkManagerExpanded = expanded; }; + DrawFoldOutGroup(extendedNetworkManager.GetType(), DisplayExtendedNetworkManagerProperties, extendedNetworkManager.ExtendedNetworkManagerExpanded, SetExpanded); + + var updatedConnectedType = (ExtendedNetworkManager.ConnectionTypes)connectionTypes.GetValue(m_ConnectionType.enumValueIndex); + if (connectionType == updatedConnectedType && updatedConnectedType == ExtendedNetworkManager.ConnectionTypes.LiveService && extendedNetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + extendedNetworkManager.ConnectionType = ExtendedNetworkManager.ConnectionTypes.Host; + } + else if (connectionType == ExtendedNetworkManager.ConnectionTypes.Host && updatedConnectedType == ExtendedNetworkManager.ConnectionTypes.LiveService && extendedNetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + extendedNetworkManager.NetworkConfig.NetworkTopology = NetworkTopologyTypes.DistributedAuthority; + } + base.OnInspectorGUI(); + } +} +#endif + + + +public class ExtendedNetworkManager : NetworkManager +{ +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool ExtendedNetworkManagerExpanded; +#endif + + public static ExtendedNetworkManager Instance; + + public enum ConnectionTypes + { + LiveService, + Host, + } + public ConnectionTypes ConnectionType; + + public int TargetFrameRate = 100; + public bool EnableVSync = false; + + [HideInInspector] + [SerializeField] + private int m_OriginalVSyncCount; + +#if UNITY_EDITOR + + protected override void OnValidateComponent() + { + m_OriginalVSyncCount = QualitySettings.vSyncCount; + base.OnValidateComponent(); + } +#endif + + private ISession m_CurrentSession; + + private string m_SessionName; + private string m_ProfileName; + private Task m_SessionTask; + + private enum ConnectionStates + { + None, + Connecting, + Connected, + } + + private ConnectionStates m_ConnectionState; + + public static string GetRandomString(int length) + { + var r = new System.Random(); + return new string(Enumerable.Range(0, length).Select(_ => (char)r.Next('a', 'z')).ToArray()); + } + + public void SetFrameRate(int targetFrameRate, bool enableVsync) + { + Application.targetFrameRate = targetFrameRate; + QualitySettings.vSyncCount = enableVsync ? m_OriginalVSyncCount : 0; + } + + private void Awake() + { + Screen.SetResolution((int)(Screen.currentResolution.width * 0.40f), (int)(Screen.currentResolution.height * 0.40f), FullScreenMode.Windowed); + SetFrameRate(TargetFrameRate, EnableVSync); + SetSingleton(); + } + + private async void Start() + { + OnClientConnectedCallback += OnClientConnected; + OnClientDisconnectCallback += OnClientDisconnect; + OnConnectionEvent += OnClientConnectionEvent; + if (UnityServices.Instance != null && UnityServices.Instance.State != ServicesInitializationState.Initialized) + { + await UnityServices.InitializeAsync(); + } + if (!AuthenticationService.Instance.IsSignedIn) + { + AuthenticationService.Instance.SignInFailed += SignInFailed; + AuthenticationService.Instance.SignedIn += SignedIn; + if (string.IsNullOrEmpty(m_ProfileName)) + { + m_ProfileName = GetRandomString(5); + } + AuthenticationService.Instance.SwitchProfile(m_ProfileName); + await AuthenticationService.Instance.SignInAnonymouslyAsync(); + } + } + + private void OnDestroy() + { + OnClientConnectedCallback -= OnClientConnected; + OnClientDisconnectCallback -= OnClientDisconnect; + OnConnectionEvent -= OnClientConnectionEvent; + } + + private void SignedIn() + { + AuthenticationService.Instance.SignedIn -= SignedIn; + Debug.Log($"Signed in anonymously with profile {m_ProfileName}"); + } + + private void SignInFailed(RequestFailedException error) + { + AuthenticationService.Instance.SignInFailed -= SignInFailed; + Debug.LogError($"Failed to sign in {m_ProfileName} anonymously: {error}"); + } + + private void OnDrawLiveServiceGUI() + { + m_SessionName = GUILayout.TextField(m_SessionName); + + if (GUILayout.Button("Create or Connect To Session")) + { + NetworkConfig.UseCMBService = true; + OnClientStopped += ClientStopped; + OnClientStarted += ClientStarted; + m_SessionTask = ConnectThroughLiveService(); + m_ConnectionState = ConnectionStates.Connecting; + LogMessage($"Connecting to session {m_SessionName}..."); + } + } + + private void OnDrawDAHostGUI() + { + if (GUILayout.Button("Start Host")) + { + OnClientStopped += ClientStopped; + OnClientStarted += ClientStarted; + StartHost(); + } + + if (GUILayout.Button("Start Client")) + { + OnClientStopped += ClientStopped; + OnClientStarted += ClientStarted; + StartClient(); + } + } + + private void OnUpdateGUIDisconnected() + { + GUILayout.BeginArea(new Rect(10, 10, 300, 800)); + + GUILayout.Label("Session Name", GUILayout.Width(100)); + + var connectionType = ConnectionType; + if (NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer && connectionType != ConnectionTypes.Host) + { + connectionType = ConnectionTypes.Host; + } + + switch (connectionType) + { + case ConnectionTypes.LiveService: + { + OnDrawLiveServiceGUI(); + break; + } + case ConnectionTypes.Host: + { + OnDrawDAHostGUI(); + break; + } + } + + GUILayout.EndArea(); + + GUILayout.BeginArea(new Rect(10, Display.main.renderingHeight - 40, Display.main.renderingWidth - 10, 30)); + var scenesPreloaded = new System.Text.StringBuilder(); + scenesPreloaded.Append("Scenes Preloaded: "); + for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) + { + var scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + scenesPreloaded.Append($"[{scene.name}]"); + } + GUILayout.Label(scenesPreloaded.ToString()); + GUILayout.EndArea(); + } + + private void OnUpdateGUIConnected() + { + if (CMBServiceConnection) + { + GUILayout.BeginArea(new Rect(10, 10, 800, 800)); + GUILayout.Label($"Session: {m_SessionName}"); + GUILayout.EndArea(); + } + else + { + GUILayout.BeginArea(new Rect(10, 10, 800, 800)); + if (DistributedAuthorityMode) + { + GUILayout.Label($"DAHosted Session"); + } + else + { + GUILayout.Label($"Client-Server Session"); + } + + GUILayout.EndArea(); + } + + GUILayout.BeginArea(new Rect(Display.main.renderingWidth - 160, 10, 150, 80)); + + if (GUILayout.Button("Disconnect")) + { + if (m_CurrentSession != null && m_CurrentSession.State == SessionState.Connected) + { + m_CurrentSession.LeaveAsync(); + m_CurrentSession = null; + } + else + { + Shutdown(); + } + } + + GUILayout.EndArea(); + } + + private void OnGUI() + { + var yAxisOffset = 10; + switch (m_ConnectionState) + { + case ConnectionStates.None: + { + yAxisOffset = 80; + OnUpdateGUIDisconnected(); + break; + } + case ConnectionStates.Connected: + { + yAxisOffset = 40; + OnUpdateGUIConnected(); + break; + } + } + + GUILayout.BeginArea(new Rect(10, yAxisOffset, 600, 800)); + if (m_MessageLogs.Count > 0) + { + GUILayout.Label("-----------(Log)-----------"); + // Display any messages logged to screen + foreach (var messageLog in m_MessageLogs) + { + GUILayout.Label(messageLog.Message); + } + GUILayout.Label("---------------------------"); + } + GUILayout.EndArea(); + } + + private void ClientStarted() + { + OnClientStarted -= ClientStarted; + m_ConnectionState = ConnectionStates.Connected; + LogMessage($"Connected to session {m_SessionName}."); + } + + private void ClientStopped(bool isHost) + { + OnClientStopped -= ClientStopped; + m_ConnectionState = ConnectionStates.None; + m_SessionTask = null; + m_CurrentSession = null; + } + + private async Task ConnectThroughLiveService() + { + try + { + var options = new SessionOptions() + { + Name = m_SessionName, + MaxPlayers = 32 + }.WithDistributedAuthorityNetwork(); + + m_CurrentSession = await MultiplayerService.Instance.CreateOrJoinSessionAsync(m_SessionName, options); + return m_CurrentSession; + } + catch (Exception e) + { + LogMessage($"{e.Message}"); + Debug.LogException(e); + } + return null; + } + + private void Update() + { + if (m_MessageLogs.Count == 0) + { + return; + } + + for (int i = m_MessageLogs.Count - 1; i >= 0; i--) + { + if (m_MessageLogs[i].ExpirationTime < Time.realtimeSinceStartup) + { + m_MessageLogs.RemoveAt(i); + } + } + } + + private void OnClientConnectionEvent(NetworkManager networkManager, ConnectionEventData eventData) + { + LogMessage($"[{Time.realtimeSinceStartup}] Connection event {eventData.EventType} for Client-{eventData.ClientId}."); + } + + private void OnClientConnected(ulong clientId) + { + LogMessage($"[{Time.realtimeSinceStartup}] Connected event invoked for Client-{clientId}."); + } + + private void OnClientDisconnect(ulong clientId) + { + LogMessage($"[{Time.realtimeSinceStartup}] Disconnected event invoked for Client-{clientId}."); + } + + private List m_MessageLogs = new List(); + + private class MessageLog + { + public string Message { get; private set; } + public float ExpirationTime { get; private set; } + + public MessageLog(string msg, float timeToLive) + { + Message = msg; + ExpirationTime = Time.realtimeSinceStartup + timeToLive; + } + } + + public void LogMessage(string msg, float timeToLive = 10.0f) + { + if (m_MessageLogs.Count > 0) + { + m_MessageLogs.Insert(0, new MessageLog(msg, timeToLive)); + } + else + { + m_MessageLogs.Add(new MessageLog(msg, timeToLive)); + } + + Debug.Log(msg); + } + + public ExtendedNetworkManager() + { + Instance = this; + } +} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs.meta new file mode 100644 index 0000000000..6c3e265735 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ExtendedNetworkManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e4d8d44c602b97b47ba488a40c66267c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs b/Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs new file mode 100644 index 0000000000..555ed20113 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs @@ -0,0 +1,363 @@ +using UnityEngine; +using Unity.Netcode.Components; +using Unity.Netcode; +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(MoverScriptNoRigidbody), true)] +[CanEditMultipleObjects] +public class MoverScriptNoRigidbodyEditor : NetworkTransformEditor +{ + private SerializedProperty m_Radius; + private SerializedProperty m_Increment; + private SerializedProperty m_RotateSpeed; + private SerializedProperty m_MovementSpeed; + private SerializedProperty m_AirSpeedFactor; + private SerializedProperty m_Gravity; + private SerializedProperty m_ContinualChildMotion; + + + public override void OnEnable() + { + m_Radius = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.SpawnRadius)); + m_Increment = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.Increment)); + m_RotateSpeed = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.RotationSpeed)); + m_MovementSpeed = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.MovementSpeed)); + m_AirSpeedFactor = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.AirSpeedFactor)); + m_Gravity = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.Gravity)); + m_ContinualChildMotion = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.ContinualChildMotion)); + + base.OnEnable(); + } + + private void DisplayerMoverScriptNoRigidbodyProperties() + { + EditorGUILayout.PropertyField(m_Radius); + EditorGUILayout.PropertyField(m_Increment); + EditorGUILayout.PropertyField(m_RotateSpeed); + EditorGUILayout.PropertyField(m_MovementSpeed); + EditorGUILayout.PropertyField(m_AirSpeedFactor); + EditorGUILayout.PropertyField(m_Gravity); + EditorGUILayout.PropertyField(m_ContinualChildMotion); + } + + public override void OnInspectorGUI() + { + var moverScriptNoRigidbody = target as MoverScriptNoRigidbody; + void SetExpanded(bool expanded) { moverScriptNoRigidbody.MoverScriptNoRigidbodyExpanded = expanded; }; + DrawFoldOutGroup(moverScriptNoRigidbody.GetType(), DisplayerMoverScriptNoRigidbodyProperties, moverScriptNoRigidbody.MoverScriptNoRigidbodyExpanded, SetExpanded); + base.OnInspectorGUI(); + } +} +#endif + +/// +/// The player controller for the player prefab +/// +public class MoverScriptNoRigidbody : NetworkTransform +{ +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool MoverScriptNoRigidbodyExpanded; +#endif + + private static bool s_EnablePlayerParentingText = true; + + [Tooltip("Radius range a player will spawn within.")] + [Range(1.0f, 40.0f)] + public float SpawnRadius = 10.0f; + + [Range(0.001f, 10.0f)] + public float Increment = 1.0f; + + [Tooltip("The rotation speed multiplier.")] + [Range(0.01f, 2.0f)] + public float RotationSpeed = 1.0f; + + [Tooltip("The forward movement speed.")] + [Range(0.01f, 30.0f)] + public float MovementSpeed = 15.0f; + + [Tooltip("The jump launching speed.")] + [Range(1.0f, 20f)] + public float JumpSpeed = 10.0f; + + [Tooltip("Determines how much the player's motion is applied when in the air.")] + [Range(0.01f, 1.0f)] + public float AirSpeedFactor = 0.35f; + + [Range(-20.0f, 20.0f)] + public float Gravity = -9.8f; + + [Tooltip("When enabled, the child spheres will continually move. When disabled, the child spheres will only move when the player moves.")] + public bool ContinualChildMotion = true; + + + private TextMesh m_ParentedText; + private PlayerColor m_PlayerColor; + private float m_JumpDelay; + private Vector3 m_WorldMotion = Vector3.zero; + private Vector3 m_CameraOriginalPosition; + private Quaternion m_CameraOriginalRotation; + private CharacterController m_CharacterController; + private PlayerBallMotion m_PlayerBallMotion; + + protected override void Awake() + { + m_ParentedText = GetComponentInChildren(); + m_ParentedText?.gameObject.SetActive(false); + m_PlayerColor = GetComponent(); + m_PlayerBallMotion = GetComponentInChildren(); + base.Awake(); + } + + /// + /// Invoked after being instantiated, we can do other pre-spawn related + /// initilization tasks here. + /// + /// + /// This provides you with a reference to the current + /// since that is not set on the until it is spawned. + /// + /// + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_CharacterController = GetComponent(); + // By default, we always disable the CharacterController and only enable it on the + // owner/authority side. + m_CharacterController.enabled = false; + base.OnNetworkPreSpawn(ref networkManager); + } + + /// + /// We are using post spawn to handle any final spawn initializations. + /// At this point we know all NetworkBehaviours on this instance has + /// been spawned. + /// + protected override void OnNetworkPostSpawn() + { + m_CharacterController.enabled = CanCommitToTransform; + if (CanCommitToTransform) + { + m_PlayerBallMotion.SetContinualMotion(ContinualChildMotion); + Random.InitState((int)System.DateTime.Now.Ticks); + transform.position += new Vector3(Random.Range(-SpawnRadius, SpawnRadius), 1.25f, Random.Range(0, SpawnRadius)); + SetState(transform.position, null, null, false); + if (IsLocalPlayer) + { + NetworkObject.DontDestroyWithOwner = false; + m_CameraOriginalPosition = Camera.main.transform.position; + m_CameraOriginalRotation = Camera.main.transform.rotation; + Camera.main.transform.SetParent(transform, false); + } + } + + if (NetworkObject.IsPlayerObject) + { + gameObject.name = $"Player-{OwnerClientId}"; + } + + m_ParentedText?.gameObject.SetActive(true); + UpdateParentedText(); + base.OnNetworkPostSpawn(); + } + + public override void OnNetworkDespawn() + { + if (IsLocalPlayer) + { + m_CharacterController.enabled = false; + Camera.main.transform.SetParent(null, false); + Camera.main.transform.position = m_CameraOriginalPosition; + Camera.main.transform.rotation = m_CameraOriginalRotation; + } + base.OnNetworkDespawn(); + } + + /// + /// Bypass NetworkTransform's OnNetworkObjectParentChanged + /// + public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) + { + if (parentNetworkObject != null) + { + Debug.Log($"Parented under {parentNetworkObject.name}"); + } + UpdateParentedText(); + base.OnNetworkObjectParentChanged(parentNetworkObject); + } + + /// + /// This method handles both client-server and distributed authority network topologies + /// client-server: If we are not the server, then we need to send an Rpc to the server to handle parenting since the Character controller is disabled on the server for all client CharacterControllers (i.e. won't trigger). + /// distributed authority: If we are the authority, then handle parenting locally. + /// + /// + public void SetParent(NetworkObject parent) + { + if ((!NetworkManager.DistributedAuthorityMode && (IsServer || (NetworkObject.AllowOwnerToParent && IsOwner))) || (NetworkManager.DistributedAuthorityMode && HasAuthority)) + { + if (parent != null) + { + NetworkObject.TrySetParent(parent); + } + else + { + NetworkObject.TryRemoveParent(); + } + } + else if (!NetworkManager.DistributedAuthorityMode && !IsServer) + { + SetParentRpc(new NetworkObjectReference(parent)); + } + } + + [Rpc(SendTo.Server)] + public void SetParentRpc(NetworkObjectReference parentReference, RpcParams rpcParams = default) + { + var parent = (NetworkObject)null; + parentReference.TryGet(out parent, NetworkManager); + if (parent != null) + { + NetworkObject.TrySetParent(parent); + } + else + { + NetworkObject.TryRemoveParent(); + } + } + + + private void Update() + { + if (!IsSpawned || !CanCommitToTransform) + { + return; + } + ApplyInput(); + } + + + private Vector3 m_PushMotion = Vector3.zero; + /// + /// Since has issues with collisions and rotating bodies, + /// we have to simulate the collision using triggers. + /// + /// + /// + /// + /// direction to push away from + public void PushAwayFrom(Vector3 normal) + { + m_PushMotion += normal * MovementSpeed * 0.10f * Time.deltaTime; + } + + /// + /// Handles player input + /// + private void ApplyInput() + { + // Simple rotation: + // Since the forward vector is perpendicular to the right vector of the player, we can just + // apply the +/- value to our forward direction and lerp our right vector towards that direction + // in order to get a reasonably smooth rotation. + var rotation = transform.forward; + m_WorldMotion = Vector3.Lerp(m_WorldMotion, m_CharacterController.isGrounded ? Vector3.zero : Vector3.up * Gravity, Time.deltaTime * 2f); + var motion = m_WorldMotion * Time.deltaTime + m_PushMotion; + var moveMotion = 0.0f; + + if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) + { + motion += transform.forward * MovementSpeed * Time.deltaTime * (m_CharacterController.isGrounded ? 1.0f : AirSpeedFactor); + moveMotion = 1.0f; + m_CharacterController.Move(motion); + } + if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) + { + motion += (transform.forward * -MovementSpeed) * Time.deltaTime * (m_CharacterController.isGrounded ? 1.0f : AirSpeedFactor); + moveMotion = -1.0f; + m_CharacterController.Move(motion); + } + + if (!m_CharacterController.isGrounded || m_JumpDelay > Time.realtimeSinceStartup || m_PushMotion.magnitude > 0.01f) + { + m_CharacterController.Move(motion); + } + + if (Input.GetKeyDown(KeyCode.Space) && m_CharacterController.isGrounded) + { + m_JumpDelay = Time.realtimeSinceStartup + 0.5f; + m_WorldMotion = motion + Vector3.up * JumpSpeed; + } + + if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) + { + transform.right = Vector3.Lerp(transform.right, rotation * RotationSpeed, Time.deltaTime).normalized; + } + if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) + { + transform.right = Vector3.Lerp(transform.right, rotation * -RotationSpeed, Time.deltaTime).normalized; + } + + // Enabled/Disable player name, transform space, and parent TextMesh + if (Input.GetKeyDown(KeyCode.P)) + { + s_EnablePlayerParentingText = !s_EnablePlayerParentingText; + } + + if (Input.GetKeyDown(KeyCode.C)) + { + ContinualChildMotion = !ContinualChildMotion; + m_PlayerBallMotion.SetContinualMotion(ContinualChildMotion); + } + + m_PushMotion = Vector3.Lerp(m_PushMotion, Vector3.zero, 0.35f); + + m_PlayerBallMotion.HasMotion(moveMotion); + } + + /// + /// Updates player TextMesh relative to each client's camera view + /// + private void OnGUI() + { + if (m_ParentedText != null) + { + if (m_ParentedText.gameObject.activeInHierarchy != s_EnablePlayerParentingText) + { + m_ParentedText.gameObject.SetActive(s_EnablePlayerParentingText); + } + if (s_EnablePlayerParentingText) + { + var position = Camera.main.transform.position; + position.y = m_ParentedText.transform.position.y; + m_ParentedText.transform.LookAt(position, transform.up); + m_ParentedText.transform.forward = -m_ParentedText.transform.forward; + } + } + } + + /// + /// Updates the contents of the parented + /// + private void UpdateParentedText() + { + if (m_ParentedText) + { + m_ParentedText.color = m_PlayerColor.Color; + if (transform.parent) + { + m_ParentedText.text = $"{gameObject.name}\n Local Space\n Parent: {transform.parent.name}"; + } + else + { + m_ParentedText.text = $"{gameObject.name}\n WorldSpace\n Parent: None"; + } + } + } +} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs.meta new file mode 100644 index 0000000000..d1b709a9fb --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/MoverScriptNoRigidbody.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5ce25b0b3f15e6446a88a85787c2f94a diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs new file mode 100644 index 0000000000..7e525d6c33 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using Unity.Netcode.Components; +using UnityEngine; +using System.Linq; + + +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(PlayerBallMotion), true)] +[CanEditMultipleObjects] +public class PlayerBallMotionEditor : NetworkTransformEditor +{ + private SerializedProperty m_RotationAxis; + private SerializedProperty m_RotationSpeed; + + + public override void OnEnable() + { + m_RotationAxis = serializedObject.FindProperty(nameof(PlayerBallMotion.RotationAxis)); + m_RotationSpeed = serializedObject.FindProperty(nameof(PlayerBallMotion.RotationSpeed)); + base.OnEnable(); + } + + private void DrawPlayerBallMotionProperties() + { + EditorGUILayout.PropertyField(m_RotationAxis); + EditorGUILayout.PropertyField(m_RotationSpeed); + } + + public override void OnInspectorGUI() + { + var playerBallMotion = target as PlayerBallMotion; + void SetExpanded(bool expanded) { playerBallMotion.ExpandPlayerBallMotion = expanded; }; + DrawFoldOutGroup< PlayerBallMotion>(playerBallMotion.GetType(), DrawPlayerBallMotionProperties, playerBallMotion.ExpandPlayerBallMotion, SetExpanded); + base.OnInspectorGUI(); + } +} +#endif + +public class PlayerBallMotion : NetworkTransform +{ +#if UNITY_EDITOR + public bool ExpandPlayerBallMotion; + public bool ExpandNetworkTransform; +#endif + public enum RotateAroundAxis + { + Up, + Right, + Forward + } + + public RotateAroundAxis RotationAxis; + public float RotationSpeed = 1.5f; + + private Vector3 m_AxisRotation = Vector3.zero; + private List m_Children; + + private bool m_ContinualMotion; + private float m_CurrentRotionMotion = 1.0f; + public void SetContinualMotion(bool continualMotion) + { + m_ContinualMotion = continualMotion; + foreach (var child in m_Children) + { + child.SetContinualMotion(continualMotion); + } + } + + protected override void Awake() + { + m_Children = GetComponentsInChildren().Where((c)=> c != this).ToList(); + base.Awake(); + } + + private void SetRotationAixs() + { + switch (RotationAxis) + { + case RotateAroundAxis.Up: + { + m_AxisRotation = transform.parent.up; + break; + } + case RotateAroundAxis.Right: + { + m_AxisRotation = transform.parent.right; + break; + } + case RotateAroundAxis.Forward: + { + m_AxisRotation = transform.parent.forward; + break; + } + } + } + + public void HasMotion(float direction) + { + if (direction == 0.0f) + { + if(!m_ContinualMotion) + { + return; + } + } + else + { + m_CurrentRotionMotion = RotationSpeed * direction; + } + + + transform.LookAt(transform.parent); + SetRotationAixs(); + transform.RotateAround(transform.parent.position, m_AxisRotation, m_CurrentRotionMotion); + foreach(var child in m_Children) + { + child.HasMotion(direction); + } + } +} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs.meta new file mode 100644 index 0000000000..407e0adf7b --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerBallMotion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 792d7ce524eb358469373fe12babef88 \ No newline at end of file diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs new file mode 100644 index 0000000000..3e2fa5b00a --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Unity.Netcode; +using UnityEngine; + + +public class PlayerColor : NetworkBehaviour +{ + private static Color[] s_Colors = { Color.red, Color.green, Color.blue, Color.cyan, Color.magenta, Color.yellow}; + public bool ApplyColorToChildren; + public Color Color { get; private set; } + public List IgnoreChildren; + + public override void OnNetworkSpawn() + { + MeshRenderer meshRenderer = GetComponent(); + ulong myId = GetComponent().OwnerClientId - (ulong)(NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection ? 1 : 0); + Color = s_Colors[myId % Convert.ToUInt64(s_Colors.Length)]; + meshRenderer.material.color = Color; + if (ApplyColorToChildren) + { + var meshRenderers = GetComponentsInChildren(); + foreach (var childMeshRenderer in meshRenderers) + { + if (IgnoreChildren != null && IgnoreChildren.Contains(childMeshRenderer.gameObject)) + { + continue; + } + childMeshRenderer.material.color = Color; + } + } + + if (IsLocalPlayer) + { + var gameObject = GameObject.Find("ServerHostClientDisplay"); + if (gameObject != null) + { + var serverHost = gameObject.GetComponent(); + serverHost?.SetColor(Color); + } + } + base.OnNetworkSpawn(); + } +} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs.meta new file mode 100644 index 0000000000..90b6ca4628 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/PlayerColor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e5128237997be649af0cc87dd0eb563 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs b/Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs new file mode 100644 index 0000000000..eaa32a77d6 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using Unity.Netcode; +using Unity.Netcode.Components; +using UnityEngine; +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(RotatingBodyLogic), true)] +[CanEditMultipleObjects] +public class RotatingBodyLogicEditor : NetworkTransformEditor +{ + private SerializedProperty m_RotationSpeed; + private SerializedProperty m_RotateDirection; + private SerializedProperty m_OnExitTransferParentOnStay; + private SerializedProperty m_PathMotion; + + + public override void OnEnable() + { + m_RotationSpeed = serializedObject.FindProperty(nameof(RotatingBodyLogic.RotationSpeed)); + m_RotateDirection = serializedObject.FindProperty(nameof(RotatingBodyLogic.RotateDirection)); + m_OnExitTransferParentOnStay = serializedObject.FindProperty(nameof(RotatingBodyLogic.OnExitTransferParentOnStay)); + m_PathMotion = serializedObject.FindProperty(nameof(RotatingBodyLogic.PathMovement)); + base.OnEnable(); + } + + private void DisplayRotatingBodyLogicProperties() + { + EditorGUILayout.PropertyField(m_RotationSpeed); + EditorGUILayout.PropertyField(m_RotateDirection); + EditorGUILayout.PropertyField(m_OnExitTransferParentOnStay); + EditorGUILayout.PropertyField(m_PathMotion); + } + + public override void OnInspectorGUI() + { + var rotatingBodyLogic = target as RotatingBodyLogic; + void SetExpanded(bool expanded) { rotatingBodyLogic.RotatingBodyLogicExpanded = expanded; }; + DrawFoldOutGroup(rotatingBodyLogic.GetType(), DisplayRotatingBodyLogicProperties, rotatingBodyLogic.RotatingBodyLogicExpanded, SetExpanded); + base.OnInspectorGUI(); + } +} +#endif + +/// +/// Handles rotating the large in-scene placed platform/tunnels and parenting/deparenting players +/// +public class RotatingBodyLogic : NetworkTransform +{ +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool RotatingBodyLogicExpanded; +#endif + + public enum RotationDirections + { + Clockwise, + CounterClockwise + } + + [Range(0.0f, 2.0f)] + public float RotationSpeed = 1.0f; + public RotationDirections RotateDirection; + public RotatingBodyLogic OnExitTransferParentOnStay; + public List PathMovement; + + private TagHandle m_TagHandle; + private float m_RotationDirection; + + private int m_CurrentPathObject = -1; + private GameObject m_CurrentNavPoint; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_TagHandle = TagHandle.GetExistingTag("Player"); + m_RotationDirection = RotateDirection == RotationDirections.Clockwise ? 1.0f : -1.0f; + SetNextPoint(); + base.OnNetworkPreSpawn(ref networkManager); + } + + private void SetNextPoint() + { + if (PathMovement == null || PathMovement.Count == 0) + { + return; + } + m_CurrentPathObject++; + m_CurrentPathObject %= PathMovement.Count; + m_CurrentNavPoint = PathMovement[m_CurrentPathObject]; + } + + + /// + /// When triggered, the player is parented under the rotating body. + /// + /// + /// This is only triggered on the owner side since we disable the CharacterController + /// on all non-owner instances. + /// + private void OnTriggerEnter(Collider other) + { + if (!IsSpawned || !other.CompareTag(m_TagHandle)) + { + return; + } + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null) + { + nonRigidPlayerMover.SetParent(NetworkObject); + } + } + + // This is used to handle NetworkObject to NetworkObject parenting detection + private List m_TriggerStayBodies = new List(); + + private void OnTriggerStay(Collider other) + { + if (!IsSpawned || !other.CompareTag(m_TagHandle)) + { + return; + } + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null) + { + if (!m_TriggerStayBodies.Contains(nonRigidPlayerMover)) + { + m_TriggerStayBodies.Add(nonRigidPlayerMover); + } + } + } + + internal bool HandleParentingForTriggerStayBodies(MoverScriptNoRigidbody moverScriptNoRigidbody) + { + if (m_TriggerStayBodies.Contains(moverScriptNoRigidbody)) + { + moverScriptNoRigidbody.SetParent(NetworkObject); + return true; + } + return false; + } + + /// + /// When triggered, the player is deparented from the rotating body. + /// + /// + /// This is only triggered on the owner side since we disable the CharacterController + /// on all non-owner instances. + /// + private void OnTriggerExit(Collider other) + { + if (!IsSpawned || !other.CompareTag(m_TagHandle)) + { + return; + } + + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null) + { + m_TriggerStayBodies.Remove(nonRigidPlayerMover); + if (OnExitTransferParentOnStay && OnExitTransferParentOnStay.HandleParentingForTriggerStayBodies(nonRigidPlayerMover)) + { + return; + } + // Otherwise, set parent back to root + nonRigidPlayerMover.SetParent(null); + } + } + + /// + /// We rotate the body during late update to avoid fighting between the host/owner (depending upon network topology) + /// motion and the body's motion/rotation. + /// + private void LateUpdate() + { + if (!IsSpawned || !CanCommitToTransform) + { + return; + } + + if (m_CurrentNavPoint != null) + { + if (Vector3.Distance(m_CurrentNavPoint.transform.position, transform.position) <= 0.05f) + { + SetNextPoint(); + } + + var direction = (m_CurrentNavPoint.transform.position - transform.position).normalized; + transform.position = Vector3.Lerp(transform.position, transform.position + direction * 10, Time.deltaTime); + } + + if (RotationSpeed > 0.0f) + { + transform.right = Vector3.Lerp(transform.right, transform.forward * m_RotationDirection, Time.deltaTime * RotationSpeed); + } + } +} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs.meta new file mode 100644 index 0000000000..ce0ea09165 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/RotatingBodyLogic.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 374ac199eb18f0f468bc018a722775c2 \ No newline at end of file diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs new file mode 100644 index 0000000000..e2ff41dfa6 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs @@ -0,0 +1,79 @@ +using Unity.Netcode; +using UnityEngine; +using UnityEngine.UI; + +public class ServerHostClientText : NetworkBehaviour +{ + private Text m_DisplayText; + + private Color m_Color; + private Color m_ColorAlpha; + private Vector3 m_LocalPosition; + + public void SetColor(Color color) + { + m_Color = color; + m_ColorAlpha = color; + m_ColorAlpha.a = 0.35f; + } + + private void Awake() + { + m_LocalPosition = transform.localPosition; + m_DisplayText = GetComponent(); + } + + private void Start() + { + if (m_DisplayText != null) + { + m_DisplayText.text = string.Empty; + SetColor(m_DisplayText.color); + } + } + + public override void OnNetworkSpawn() + { + if (m_DisplayText != null) + { + if (NetworkManager.IsServer) + { + m_DisplayText.text = NetworkManager.IsHost ? "Host" : "Server"; + } + else if (NetworkManager.IsClient) + { + m_DisplayText.text = $"Client-{NetworkManager.LocalClientId}"; + } + } + transform.localPosition = m_LocalPosition; + } + + public override void OnNetworkDespawn() + { + if (m_DisplayText != null) + { + m_DisplayText.text = string.Empty; + } + base.OnNetworkDespawn(); + } + + private bool m_LastFocusedValue; + private void OnGUI() + { + if (!IsSpawned || m_LastFocusedValue == Application.isFocused) + { + return; + } + + m_LastFocusedValue = Application.isFocused; + + if (m_LastFocusedValue) + { + m_DisplayText.color = m_Color; + } + else + { + m_DisplayText.color = m_ColorAlpha; + } + } +} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs.meta new file mode 100644 index 0000000000..081f5a96a8 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/ServerHostClientText.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6637cd674efb56a48a3d4d545d23a8d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs b/Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs new file mode 100644 index 0000000000..ade22f3ed6 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs @@ -0,0 +1,63 @@ +using UnityEngine; + +/// +/// This helper class is used to push a player away from a rotating body. +/// s without a don't +/// handle collision with rotating bodies. This simulates a "collision". +/// +public class TriggerPush : MonoBehaviour +{ + public enum RightOrLeft + { + Right, + Left + } + + [Tooltip("Determines if this trigger will push the player to the left or right of the root transform")] + public RightOrLeft PushDirection; + + private TagHandle m_TagHandle; + + private void Awake() + { + m_TagHandle = TagHandle.GetExistingTag("Player"); + } + + private void PushObject(Collider other, bool isInside = false) + { + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null && nonRigidPlayerMover.CanCommitToTransform) + { + // We determine the direction to push and if within a trigger we push a little more to prevent from + // completely clipping through the object. + var direction = (PushDirection == RightOrLeft.Right ? 1.0f : -1.0f) * (isInside ? 1.75f : 1.0f); + nonRigidPlayerMover.PushAwayFrom(transform.parent.right * direction); + } + } + + /// + /// Pushes the player away from the object + /// + private void OnTriggerEnter(Collider other) + { + if (!other.CompareTag(m_TagHandle)) + { + return; + } + PushObject(other); + } + + /// + /// When the trigger is in a "stay" state, we need to signal that + /// the amount to "push away" should be increased. + /// + /// + private void OnTriggerStay(Collider other) + { + if (!other.CompareTag(m_TagHandle)) + { + return; + } + PushObject(other, true); + } +} diff --git a/Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs.meta b/Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs.meta new file mode 100644 index 0000000000..7c07b1ebfc --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Scripts/TriggerPush.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2c40721ca0fd31645a742e5ad0e0cdc5 \ No newline at end of file diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures.meta b/Examples/CharacterControllerMovingBodies/Assets/Textures.meta new file mode 100644 index 0000000000..84caff0700 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Textures.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 22c34a08d52a0644fae5e90dbcc0ba52 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/GridPattern.png b/Examples/CharacterControllerMovingBodies/Assets/Textures/GridPattern.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f648aacc6ce68697cb397decbeb4207aecbd0a GIT binary patch literal 3152344 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 literal 0 HcmV?d00001 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/GridPattern.png.meta b/Examples/CharacterControllerMovingBodies/Assets/Textures/GridPattern.png.meta new file mode 100644 index 0000000000..305bd2392c --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Textures/GridPattern.png.meta @@ -0,0 +1,153 @@ +fileFormatVersion: 2 +guid: a092c5fa8c60ed04aa1d72555f1740bc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Windows Store Apps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_BaseMap.png b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_BaseMap.png new file mode 100644 index 0000000000000000000000000000000000000000..3f311d2225e026025a38ecae433481f37b4ba2e1 GIT binary patch literal 8210 zcmeHLeM}qY8GoUqkaQ@Cx~=IF!OiK`P0jf}{C&fiuY|CLj|HTBjE0==&ftN4j%~o0 z#*lVux=n>mU6rk4Gt;DwuA;O-30)YqyJ%$-S;#~O%VXQaUdZU{!G^W{(bnf(X{uF z1%O8$75-zuD^1$~h|kj(9kd?I-A6IJK}@r}mJ{0yCP)oHipFN5nR3pGXt`3oQ7xM2 zbcql>s}>!Q<>I-fOs-77*KXzt?RiCvy_`|8B26liVxu4f181cXo1wyJp=@eVh%W{2 zg=I{Hgh;IAYEil%5ILAzfMoJ!4v~v-l)-U1qELwmxl|#O>_SMKBru%B2pLKcl#HYZ z2@?4eL1|`INA1tr6Ojd<)S@!0)kI-fb#=A4S|aAnr5K@7sRSM*i9!n0Qe(8zHq>a@ zxyT`lvoL18$*Sj#h`^E7@|9M#2x=OJU@%458Z8ksL4#p7+Jq5eT!0iJWSJkK-1>tMrGVq7lR=x!eZTB1z|)Ca$9Ok zQH_bi_H!1#(#&wXtDu@Y1vDlq)6CIU-dx1<6^o84SX3DyNil(Z?SS6M^3|5FFWbOn z(N<0^61;}uB#KLma3v)rDN^F_L+vEX*K;y63(UMuUjbiO_Gf1xIlD9E zgi0<)A;yrpxw%x1(PE{I440Fo7D3~P^?H_)Nk|bEa;}0_Eaa$@$ygR=RH%Z(wWvfTm7}zlCQ&7WOKGWuRNzWZ2>T_;i(Jwn zJ`41?C6ZPdbZO+SLZ7DfHzhW(BsI@)o-4&Z;vsE9Z1NC8}c-Qk2w6q5n7*=7^l&NRq)h z9SLJNtkX)>39^WgQ&K4#K^ln*GSd3)YX@uKoi-Ql*CdnD0oy@QW`iJHU0ND23oUHUBThsVI z-gB`d?WAtl2oBR>uNU(gDpHcF?B-3B&g^gn5FFZNAt6 zgYXCI(y8yao_RGJfDcn(4!crQoW1XU8z}oeHme#uK}Y`PSodpl4+|;x&iwS8EoINO zqnF03+Wor*vCg51lWjq~+jYU$W9<0(5G1-!^7L-PAYzt_p6k#!02b%j zkm~azGtXT7%R}zh#HV_W?U?Y_+P8o_ueW=++e47?h5d(5_5AT_eCrv<#N?t`(>#^eRa?di6Gi3xs?pKUr`7h5zO*RZ|##CYQm+uWn2&UwY> z3Vil1UHqHDbZ7EAFk#ky55F57qa`yIocpNc^1#5r&4hjC&=AvpJlyn85NAt#6Rw54 zM)wn;3Yj|i7{ni*hwh`1W_5E^mbZ>jGrRW7qo+ zM}VC=eij2uI75%?1?rMNXWsJ9d;jUIn}2-A1^}i@rnWam(>#i}2Z$sLZQ7;^biQ0)3 z%W%uuJB;0fe>}CDGuJhN7GxZ5%%w|U_1x@^={02&eV&e`HlDp zue5uHG>6*TG!Acfhh}u@{DM#m@_UCZ#w3sjlfJ=Ya}D)92TZ?h)1+#W8h@<%>FNG+ z#bRG-S*y49q;sxp&?$R6=u!ULA0TGWXachyT}j>Cw7|{Y<*2J0bo3361s-jto-}>% z;iihZ;%7D-(FAU~==#$Gqq1+*k8hrxn}lU&eMD5GVu0bT;L=wXke(Y@d+>CxkVrz| zc<=A2tZJTvlfvcoGaQ8vOApSMkm{}@zsWv&K`0tWpZAnpyN(5#;1KAycfAUi1vf8S zhH-trpBcmc{p_Uie<@HaP8!Ke#Uk`E!o6+qytK{o$s04tmu^0+>38F{+on@7~pksxPxUe+5KN1y#KM4*e` literal 0 HcmV?d00001 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_BaseMap.png.meta b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_BaseMap.png.meta new file mode 100644 index 0000000000..3a05497054 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_BaseMap.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: e71f43865e91e6b418bd0d67be2445dc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Emissive.png b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Emissive.png new file mode 100644 index 0000000000000000000000000000000000000000..df81ac1268d54bf0f6eb188573559529a04414c9 GIT binary patch literal 8225 zcmeGgdr%YU{X?;!Ry}Jw$DE$5VcOo+ZuUXmC5Z?WnK43<7O~X0>~0_jWJ40x|3|r|5XXAp*{&o{z{xMj%2+(r@!3SoOU7OR$;A zx8HvI`+o1&_x&+zQ~HucZ!7`;EXmlAnhiiSoJND#dGPVj`mfL7W1(?Fo*96}EBJpD z*nc1nfS67CoGq3unvDvYD-}`J3VP^Gt^T(KAXx zu2_R>j45o9enYj1-CVsXhpsNB<%~eBLXs*K5J4$xp^(bb5`$S$sTBC}D&U-7#sr8T z#8RvjB=ZE3Et)JOg)^~;M2Mp_j!O`!Tu4Yn5|Qku2#J#fh7%Y;qJ&r>CKaR{3H}6- znu*aVvQvK^qy^uUf+CB>sKBs_iV9&xqL4EcVuV~S$8ZuONfbh$<|>1QszeRugb;>Q z)=Zo9MvI;^AUsB@fGf8s1(4H#2&Kj_T7x;LCa5s1k}_h15a&hm12S|N&RA|L@e^lg zj4feHS%bw4VTmxTv52#9<|6JHsKcLU5P+)HXu>p}%1dc!n1tDqwgZY0RLE1I%{f&@ z7RzSMT)By6({?~M6L@KiiWCz|SvXS;$CZQ(l@($cAxR;Dyp^jrFkFTC?T8I*DrI4n z0^VvUPNKLd2bU>C&}!nfIHADt0H}sz^t!5TI1?lcxl4>(&qzthGI2V63H)HrPG65?q@_p*xkQ3O z8U5^PG>Qy^*+Lm;HX~IjfXWf-^^77>L^3itl#@<`l8PCT45cKL4AmvlGWe4+GMo)VoJ?k>^9t-KqZ_7h9Od%x=0^UF*GmmR79!Tc$gs4@E&Z~kRt zt;KhG$Qc8`m50suq5x=k6blP->~c7~CV&I-pF6(#N>y`HC;iU%Tk>tWHQmQN$NGnN ztm?FBJ6d9>-`TY~H{0vD&hgNxa9H;TI5fNlk%2XQQ?Q;A57XXkwCyjd0a8#^>F?MI7tYgS(g>ky`AHdjRv)Syj_KuF$n5bJh_4A?T)~J1B z6C;(oKRbWWG4_r#@1%CenSrHRH`Qp}H@x>Zw%iUr_GeqnKHDV@{kmAw1i*pL>u}@+ z%R1%yV09FbH64zhWr^t(7cywQp8YedchKNeLdQ3*&Q;Hz67y_J2868mB)=scdhR7v zwXtEOwdv91wST!Y-$Uz7r)$VJTC2MA6^w7KR@+%tYqe%B zn|JQRdavV=eI#z*&bNz?c6Vpi#DCm#qb5^%W4v&Q!6$2kb$fUHaCf`+bYE}0+6&RO zlAfN|AOE`c-QI4MCo}HggR$yMOSijs4ytSCTHKtgiUyWX zJC1dDCt>CPk=)Ck)Q%6fgG2o_nN8>7`Z4#NW@}6O_CNbO9Ov+2XupWYXqFeqSELKz zEmmH9G}zvB4fekdrn?>gYpBtkJNBG{9esy-%HtY>-2ebSF4O|JF1TDS`^ewkcyD~>s9YsDfP94wpl=<8F`{RSx5@2spHU`Jp+}cOv-Dy_{cDi#> zNWVwl7w+vEjoqEc_d(FAUHKl?c^m`xx7m8(avw}_k;`+%z5&sDw$60i&xg;prUP)< zejkpr^n4oO#1Zy|{@jUt-gix|9WuT2cya&@!iktX|AFc3DHut7dc-e|ujv}Wj(d)|#^EXR<-g{5VIE(l`;e~N zq;ekZ&l-uVd}OY*&;4L=LtC!{9 zD-J4tY`l4EQHeFbcEL8adsIy|9D^$VS;O!$c*vPE(G?)?UBwOW$hW0{5k__NZc;%iu?l Nk+v!IqxIW=`(MOlo)iE8 literal 0 HcmV?d00001 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Emissive.png.meta b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Emissive.png.meta new file mode 100644 index 0000000000..bcfe13d8d7 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Emissive.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: 8bf2cf149563066489e749ea032dbca7 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Normal.png b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Normal.png new file mode 100644 index 0000000000000000000000000000000000000000..1d060fba6157919ffa347e9d580c5303191c7f67 GIT binary patch literal 21103 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 Ncdx%xefjk_{|^NY)U*Hq literal 0 HcmV?d00001 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Normal.png.meta b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Normal.png.meta new file mode 100644 index 0000000000..4a5b2ad61c --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_01_Normal.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: b94463ba36040ec4082132c54dd1bbad +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 0 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 1 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_02_BaseMap.png b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_02_BaseMap.png new file mode 100644 index 0000000000000000000000000000000000000000..b3069b03d14bb6dd04f8050c0378123250ce6c76 GIT binary patch literal 9489 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 G%6|c^ch-ae literal 0 HcmV?d00001 diff --git a/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_02_BaseMap.png.meta b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_02_BaseMap.png.meta new file mode 100644 index 0000000000..7fbedef673 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Assets/Textures/Grid_02_BaseMap.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: 844faea9c35b2464c8ecb09f66e2e2f8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/CharacterControllerMovingBodies/Packages/manifest.json b/Examples/CharacterControllerMovingBodies/Packages/manifest.json new file mode 100644 index 0000000000..de9d632e57 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Packages/manifest.json @@ -0,0 +1,55 @@ +{ + "dependencies": { + "com.unity.2d.sprite": "1.0.0", + "com.unity.2d.tilemap": "1.0.0", + "com.unity.ads": "4.4.2", + "com.unity.ai.navigation": "2.0.3", + "com.unity.analytics": "3.8.1", + "com.unity.collab-proxy": "2.4.4", + "com.unity.feature.development": "1.0.2", + "com.unity.ide.rider": "3.0.31", + "com.unity.ide.visualstudio": "2.0.22", + "com.unity.netcode.gameobjects": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", + "com.unity.purchasing": "4.12.2", + "com.unity.services.multiplayer": "1.0.0-pre.1", + "com.unity.test-framework": "1.4.5", + "com.unity.timeline": "1.8.7", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.9", + "com.unity.transport": "2.3.0", + "com.unity.ugui": "2.0.0", + "com.unity.visualscripting": "1.9.4", + "com.unity.xr.legacyinputhelpers": "2.1.10", + "com.unity.modules.accessibility": "1.0.0", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/Examples/CharacterControllerMovingBodies/ProjectSettings/EditorBuildSettings.asset b/Examples/CharacterControllerMovingBodies/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000000..a6cd971819 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,12 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: + - enabled: 1 + path: Assets/Scenes/CharacterController.unity + guid: 9fc0d4010bbf28b4594072e72b8655ab + m_configObjects: {} + m_UseUCBPForAssetBundles: 0 diff --git a/Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectSettings.asset b/Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000000..b12ab6ff29 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,687 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 28 + productGUID: 6859c206e4885eb4d9a885722be3daa1 + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: CharacterControllerMovingBodies + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.12156863, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1920 + defaultScreenHeight: 1080 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 0 + unsupportedMSAAFallback: 0 + m_SpriteBatchVertexThreshold: 300 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 1 + androidBlitType: 0 + androidResizeableActivity: 1 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 0 + androidApplicationEntry: 2 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 0 + captureSingleScreen: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + dedicatedServerOptimizations: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 1 + meshDeformation: 1 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchMaxWorkerMultiple: 8 + switchNVNGraphicsFirmwareMemory: 32 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 0 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 + bundleVersion: 1.0 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 + useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 + androidMinAspectRatio: 1 + applicationIdentifier: + Standalone: com.DefaultCompany.CharacterControllerMovingBodies + buildNumber: + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 0 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 23 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + androidSplitApplicationBinary: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 13.0 + tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 13.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + metalCompileShaderBinary: 0 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: 00000000000000000000000000000000 + templatePackageId: + templateDefaultScene: + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 2 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidEnableArm64MTE: 0 + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 200 + AndroidReportGooglePlayAppDependencies: 1 + androidSymbolsSizeThreshold: 800 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: [] + m_BuildTargetBatching: [] + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: [] + m_BuildTargetGraphicsJobMode: [] + m_BuildTargetGraphicsAPIs: [] + m_BuildTargetVRSettings: [] + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + VisionOS: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: [] + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: [] + m_BuildTargetDefaultTextureCompressionFormat: [] + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 11.0 + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 1 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 + switchUseNewStyleFilepaths: 1 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + switchUpgradedPlayerSettingsToNMETA: 0 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 2 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 2 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 32 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 1 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLEnableWebGPU: 0 + webGLPowerPreference: 2 + webGLWebAssemblyTable: 0 + webGLWebAssemblyBigInt: 0 + webGLCloseOnQuit: 0 + webWasm2023: 0 + scriptingDefineSymbols: {} + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: {} + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + il2cppStacktraceInformation: {} + managedStrippingLevel: {} + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + editorAssembliesCompatibilityLevel: 1 + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: CharacterControllerMovingBodies + metroPackageVersion: 1.0.0.0 + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: CharacterControllerMovingBodies + wsaImages: {} + metroTileShortName: CharacterControllerMovingBodies + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} + metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} + cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 0 + embeddedLinuxEnableGamepadInput: 0 + hmiCpuConfiguration: + hmiLogStartupTiming: 0 + qnxGraphicConfPath: + apiCompatibilityLevel: 6 + captureStartupLogs: {} + activeInputHandler: 0 + windowsGamepadBackendHint: 0 + cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: + organizationId: + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 + androidVulkanDenyFilterList: [] + androidVulkanAllowFilterList: [] diff --git a/Examples/CharacterControllerMovingBodies/Readme.md b/Examples/CharacterControllerMovingBodies/Readme.md new file mode 100644 index 0000000000..2403b27a35 --- /dev/null +++ b/Examples/CharacterControllerMovingBodies/Readme.md @@ -0,0 +1,27 @@ +# Netcode for GameObjects Smooth Transform Space Transitions +## Non-Rigidbody CharacterController Parenting with Moving Bodies +![image](https://github.com/user-attachments/assets/096953ca-5d7d-40d5-916b-72212575d258) +This example provides you with the fundamental building blocks for smooth synchronized transitions between two non-rigidbody based objects. This includes transitioning from world to local, local to world, and local to local transform spaces. + +### The `CharacterController` +![image](https://github.com/user-attachments/assets/13c627bd-920d-40c8-8947-69aa37b44ebf) +The `CharacterController` component is assigned to the `PlayerNoRigidbody` player prefab. It includes a `MoverScriptNoRigidbody` that handles all of the player's motion and includes some additional "non-rigidbody to non-rigidbody" collision handling logic that is applied when a player bumps into a rotation and/or moving body. The player prefab includes a child "PlayerBallPrime" that rotates around the player in local space (nested `NetworkTransform`), and the "PlayerBallPrime" has 3 children ("PlayerBallChild1-3") that each rotates around a different axis of the "PlayerBallPrime". While the end resulting effect is kind of cool looking, they provide a point of reference as to whether there is any deviation of each child's given axial path relative to each parent level. Additionally, it shows how tick synchronized nested `NetworkTransform` components keep synchronized with their parent and how that persists when the parent is parented or has its parent removed. + +### Rotating Bodies +#### StationaryBodyA&B +The stationary bodies are representative of changing the parenting of a `NetworkObject` between two `NetworkObject` instances while remaining in local space, interpolating, and smoothly transitioning between the two parents. +![image](https://github.com/user-attachments/assets/1b234fdc-efc9-4053-a947-531c4fe5dd96) + +#### RotatingBody +The RotatingBody are just provide an example of a rotating non-Rigidbody object and one way to handle a rotation based collision. This also tests world to local and local to world transform space updates. +![image](https://github.com/user-attachments/assets/f5da2374-08b6-4eeb-9a4a-cddc67ecb33b) + +#### MovingRotatingBody & ElevatorBody +Both of these bodies use a simple path to follow. +The MovingRotatingBody moves between two points on the world Z-axis and rotates while moving. The ElevatorBody moves between 4 points (forming a rectangular path) to show motion on more than one axis. +![image](https://github.com/user-attachments/assets/146912eb-0dcc-4089-a6ba-3e0dbb51fd4e) + + +### Example Limitations +This example is primarily to provide a starting point for anyone interested in exploring a non-Rigidbody motion based project and is not to be considered a full working solution but more of a place to start. As an example. you might notice there is no consideration of Y-axis motion when on the elevator so it impacts the `MoverScriptNoRigidbody` jumping. This could be extended to take into consideration the bodies within the `RotatingBodyLogic` script that would increase the upward velocity amount when on a moving body that does move in the Y Axis. This could be further extended by adding a `MonoBehaviour` component to the path points that define its direction of movement or by a normalized direction vector between points to determine the movement of the body. These kind of details are left up to you to determine what style best fits your project. + From 7389f8b7ed8e377588da9173e313e00e62833c5a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 9 Sep 2024 14:36:02 -0500 Subject: [PATCH 096/236] fix: networkshow client connected scenemanagement disabled (#3056) * fix Fixing issue where NetworkSpawnManager.ShowHiddenObjectsToNewlyJoinedClient was being invoked on the session owner when scene management is disabled. * test Adding an additional test for NetworkSpawnManager.ShowHiddenObjectsToNewlyJoinedClient validation test that runs with scene management disabled. Adding some additional checks within NetcodeIntegrationTestHelpers to skip any NetworkManagers that were destroyed prior to the final shutdown sequence. --- .../Messages/ClientConnectedMessage.cs | 5 +++- .../Runtime/NetcodeIntegrationTestHelpers.cs | 30 ++++++++++++------- .../ExtendedNetworkShowAndHideTests.cs | 23 +++++++++++--- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index d1cd7e43eb..ef7ef5f9a1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -59,7 +59,10 @@ public void Handle(ref NetworkContext context) // - Hidden from the session owner // - Owned by this client // - Has NetworkObject.SpawnWithObservers set to true (the default) - networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId); + if (!networkManager.LocalClient.IsSessionOwner) + { + networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId); + } // We defer redistribution to the end of the NetworkUpdateStage.PostLateUpdate networkManager.RedistributeToClient = true; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index ed41fc18f9..8758db8629 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -326,21 +326,31 @@ public static void Destroy() s_IsStarted = false; - // Shutdown the server which forces clients to disconnect - foreach (var networkManager in NetworkManagerInstances) + try { - networkManager.Shutdown(); - s_Hooks.Remove(networkManager); - } + // Shutdown the server which forces clients to disconnect + foreach (var networkManager in NetworkManagerInstances) + { + if (networkManager != null && networkManager.IsListening) + { + networkManager?.Shutdown(); + s_Hooks.Remove(networkManager); + } + } - // Destroy the network manager instances - foreach (var networkManager in NetworkManagerInstances) - { - if (networkManager.gameObject != null) + // Destroy the network manager instances + foreach (var networkManager in NetworkManagerInstances) { - Object.DestroyImmediate(networkManager.gameObject); + if (networkManager != null && networkManager.gameObject) + { + Object.DestroyImmediate(networkManager.gameObject); + } } } + catch (Exception ex) + { + Debug.LogException(ex); + } NetworkManagerInstances.Clear(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs index 61d71c0d4d..1ebb0f0411 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs @@ -6,23 +6,38 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.DAHost, true)] + [TestFixture(HostOrServer.DAHost, false)] public class ExtendedNetworkShowAndHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 3; - + private bool m_EnableSceneManagement; private GameObject m_ObjectToSpawn; private NetworkObject m_SpawnedObject; private NetworkManager m_ClientToHideFrom; private NetworkManager m_LateJoinClient; private NetworkManager m_SpawnOwner; - public ExtendedNetworkShowAndHideTests(HostOrServer hostOrServer) : base(hostOrServer) { } + public ExtendedNetworkShowAndHideTests(HostOrServer hostOrServer, bool enableSceneManagement) : base(hostOrServer) + { + m_EnableSceneManagement = enableSceneManagement; + } protected override void OnServerAndClientsCreated() { + if (!UseCMBService()) + { + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + } + + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + } + m_ObjectToSpawn = CreateNetworkObjectPrefab("TestObject"); m_ObjectToSpawn.SetActive(false); + base.OnServerAndClientsCreated(); } @@ -81,7 +96,7 @@ private bool IsClientPromotedToSessionOwner() protected override void OnNewClientCreated(NetworkManager networkManager) { m_LateJoinClient = networkManager; - + networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; networkManager.NetworkConfig.Prefabs = m_SpawnOwner.NetworkConfig.Prefabs; base.OnNewClientCreated(networkManager); } From 1d228042487e3452017675c36facb347ede24298 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 9 Sep 2024 18:01:49 -0500 Subject: [PATCH 097/236] chore: final 2.0.0 clean up tasks (#3052) * chore Add unique message when invoking NetworkConnectionManager.DisconnectClient while in a distributed authority session. * fix Make the NetworkTransformEditor use NetworkTransform as the base type so it doesn't show a foldout group when using a non-derived component. * fix Adding ToolTips to NetworkObject (MTTB-184) * fix Just log a warning when developer logging is enabled if a client or server receives a universal RPC message for a NetworkObject that does not exist. * fix Making NetworkAnimator.Awake a protected and virtual method. * style removing using directive. * update Adding changelog entries. * style updating XML API documentation for `NetworkTransform.Teleport` --- com.unity.netcode.gameobjects/CHANGELOG.md | 5 +++++ .../Editor/NetworkTransformEditor.cs | 2 +- .../Runtime/Components/NetworkAnimator.cs | 2 +- .../Runtime/Components/NetworkTransform.cs | 6 +++++- .../Connection/NetworkConnectionManager.cs | 10 +++++++++- .../Runtime/Core/NetworkObject.cs | 11 ++++++++++- .../Messaging/Messages/ProxyMessage.cs | 19 +++++-------------- .../Runtime/Messaging/Messages/RpcMessages.cs | 8 +++++++- 8 files changed, 43 insertions(+), 20 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d618645fdc..1a0766b5f1 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added tooltips for all of the `NetworkObject` component's properties. (#3052) - Added message size validation to named and unnamed message sending functions for better error messages. (#3049) - Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3031) - Added `NetworkTransform.SwitchTransformSpaceWhenParented` property that, when enabled, will handle the world to local, local to world, and local to local transform space transitions when interpolation is enabled. (#3013) @@ -20,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3052) - Fixed issue where a NetworkObject hidden from a client that is then promoted to be session owner was not being synchronized with newly joining clients.(#3051) - Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045) - Fixed issue where setting a prefab hash value during connection approval but not having a player prefab assigned could cause an exception when spawning a player. (#3042) @@ -30,6 +32,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed `NetworkTransformEditor` now uses `NetworkTransform` as the base type class to assure it doesn't display a foldout group when using the base `NetworkTransform` component class. (#3052) +- Changed `NetworkAnimator.Awake` is now a protected virtual method. (#3052) +- Changed when invoking `NetworkManager.ConnectionManager.DisconnectClient` during a distributed authority session a more appropriate message is logged. (#3052) - Changed `NetworkTransformEditor` so it now derives from `NetcodeEditorBase`. (#3013) - Changed `NetworkRigidbodyBaseEditor` so it now derives from `NetcodeEditorBase`. (#3013) - Changed `NetworkManagerEditor` so it now derives from `NetcodeEditorBase`. (#3013) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 7d8b26522c..e93226e2bd 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -9,7 +9,7 @@ namespace Unity.Netcode.Editor ///
[CustomEditor(typeof(NetworkTransform), true)] [CanEditMultipleObjects] - public class NetworkTransformEditor : NetcodeEditorBase + public class NetworkTransformEditor : NetcodeEditorBase { private SerializedProperty m_SwitchTransformSpaceWhenParented; private SerializedProperty m_TickSyncChildren; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index e01f4e7795..ca3218a1a2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -584,7 +584,7 @@ public override void OnDestroy() base.OnDestroy(); } - private void Awake() + protected virtual void Awake() { int layers = m_Animator.layerCount; // Initializing the below arrays for everyone handles an issue diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 13fb21a5ce..b9d8eabab3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3604,8 +3604,12 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool } /// - /// Teleport the transform to the given values without interpolating + /// Teleport an already spawned object to the given values without interpolating. /// + /// + /// This is intended to be used on already spawned objects, for setting the position of a dynamically spawned object just apply the transform values prior to spawning.
+ /// With player objects, override the method and have the authority make adjustments to the transform prior to invoking base.OnNetworkSpawn. + ///
/// new position to move to. /// new rotation to rotate to. /// new scale to scale to. diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index af97de8d06..b5fff5404d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1299,7 +1299,15 @@ internal void DisconnectClient(ulong clientId, string reason = null) { if (!LocalClient.IsServer) { - throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead."); + if (NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead."); + } + else + { + Debug.LogWarning($"Currently, clients cannot disconnect other clients from a distributed authority session. Please use `{nameof(Shutdown)}()` instead."); + return; + } } if (clientId == NetworkManager.ServerClientId) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 591fd417e7..f89bc1d345 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -938,6 +938,7 @@ private bool InternalHasAuthority() /// /// If true, the object will always be replicated as root on clients and the parent will be ignored. /// + [Tooltip("If enabled (default disabled), instances of this NetworkObject will ignore any parent(s) it might have and replicate on clients as the root being its parent.")] public bool AlwaysReplicateAsRoot; /// @@ -955,6 +956,8 @@ private bool InternalHasAuthority() /// bandwidth cost. This can also be useful for UI elements that have /// a predetermined fixed position. /// + [Tooltip("If enabled (default enabled), newly joining clients will be synchronized with the transform of the associated GameObject this component is attached to. Typical use case" + + " scenario would be for managment objects or in-scene placed objects that don't move and already have their transform settings applied within the scene information.")] public bool SynchronizeTransform = true; /// @@ -1012,6 +1015,7 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// To synchronize clients of a 's scene being changed via , /// make sure is enabled (it is by default). /// + [Tooltip("When enabled (default disabled), spawned instances of this NetworkObject will automatically migrate to any newly assigned active scene.")] public bool ActiveSceneSynchronization; /// @@ -1030,6 +1034,7 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// is and is and the scene is not the currently /// active scene, then the will be destroyed. /// + [Tooltip("When enabled (default enabled), dynamically spawned instances of this NetworkObject's migration to a different scene will automatically be synchonize amongst clients.")] public bool SceneMigrationSynchronization = true; /// @@ -1045,7 +1050,7 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// /// When set to false, the NetworkObject will be spawned with no observers initially (other than the server) /// - [Tooltip("When false, the NetworkObject will spawn with no observers initially. (default is true)")] + [Tooltip("When disabled (default enabled), the NetworkObject will spawn with no observers. You control object visibility using NetworkShow. This applies to newly joining clients as well.")] public bool SpawnWithObservers = true; /// @@ -1074,11 +1079,13 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// Whether or not to destroy this object if it's owner is destroyed. /// If true, the objects ownership will be given to the server. /// + [Tooltip("When enabled (default disabled), instances of this NetworkObject will not be destroyed if the owning client disconnects.")] public bool DontDestroyWithOwner; /// /// Whether or not to enable automatic NetworkObject parent synchronization. /// + [Tooltip("When disabled (default enabled), NetworkObject parenting will not be automatically synchronized. This is typically used when you want to implement your own custom parenting solution.")] public bool AutoObjectParentSync = true; /// @@ -1091,12 +1098,14 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// When using a network topology and an owner authoritative motion model, disabling this can help smooth parenting transitions. /// When using a network topology this will have no impact on the owner's instance since only the authority/owner can parent. /// + [Tooltip("When disabled (default enabled), the owner will not apply a server or host's transform properties when parenting changes. Primarily useful for client-server network topology configurations.")] public bool SyncOwnerTransformWhenParented = true; /// /// Client-Server specific, when enabled an owner of a NetworkObject can parent locally as opposed to requiring the owner to notify the server it would like to be parented. /// This behavior is always true when using a distributed authority network topology and does not require it to be set. /// + [Tooltip("When enabled (default disabled), owner's can parent a NetworkObject locally without having to send an RPC to the server or host. Only pertinent when using client-server network topology configurations.")] public bool AllowOwnerToParent; internal readonly HashSet Observers = new HashSet(); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs index c7322c499e..57c8345175 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -1,4 +1,3 @@ -using System; using Unity.Collections; namespace Unity.Netcode @@ -34,21 +33,13 @@ public unsafe void Handle(ref NetworkContext context) var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(WrappedMessage.Metadata.NetworkObjectId, out var networkObject)) { - // With distributed authority mode, we can send Rpcs before we have been notified the NetworkObject is despawned. - // DANGO-TODO: Should the CMB Service cull out any Rpcs targeting recently despawned NetworkObjects? - // DANGO-TODO: This would require the service to keep track of despawned NetworkObjects since we re-use NetworkObject identifiers. - if (networkManager.DistributedAuthorityMode) + // If the NetworkObject no longer exists then just log a warning when developer mode logging is enabled and exit. + // This can happen if NetworkObject is despawned and a client sends an RPC before receiving the despawn message. + if (networkManager.LogLevel == LogLevel.Developer) { - if (networkManager.LogLevel == LogLevel.Developer) - { - NetworkLog.LogWarning($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); - } - return; - } - else - { - throw new InvalidOperationException($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); + NetworkLog.LogWarning($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}] An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); } + return; } var observers = networkObject.Observers; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index ba207e1046..70e4c2aadf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -60,7 +60,13 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(metadata.NetworkObjectId, out var networkObject)) { - throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); + // If the NetworkObject no longer exists then just log a warning when developer mode logging is enabled and exit. + // This can happen if NetworkObject is despawned and a client sends an RPC before receiving the despawn message. + if (networkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"[{metadata.NetworkObjectId}, {metadata.NetworkBehaviourId}, {metadata.NetworkRpcMethodId}] An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); + } + return; } var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId); From 31c30e52d0a8a32cda6a4960149dcee1c7db13ed Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 10 Sep 2024 16:38:17 -0500 Subject: [PATCH 098/236] fix: networkanimator only updates observers (#3057) * fix Only send updates to observers. * test Includes validation test that it only sends updates to observers. * update adding changelog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Components/NetworkAnimator.cs | 44 ++++++--- .../Runtime/Animation/NetworkAnimatorTests.cs | 97 +++++++++++++++++++ 3 files changed, 131 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 1a0766b5f1..274ed9e951 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3057) - Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3052) - Fixed issue where a NetworkObject hidden from a client that is then promoted to be session owner was not being synchronized with newly joining clients.(#3051) - Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index ca3218a1a2..16b77ca5af 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -952,8 +952,14 @@ internal void CheckForAnimatorChanges() { // Just notify all remote clients and not the local server m_ClientSendList.Clear(); - m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); - m_ClientSendList.Remove(NetworkManager.LocalClientId); + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (clientId == NetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId)) + { + continue; + } + m_ClientSendList.Add(clientId); + } m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams); } @@ -1264,9 +1270,15 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) { m_ClientSendList.Clear(); - m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); - m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId); - m_ClientSendList.Remove(NetworkManager.ServerClientId); + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) + { + continue; + } + m_ClientSendList.Add(clientId); + } + m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_ClientRpcParams); } @@ -1321,9 +1333,14 @@ private void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpc if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) { m_ClientSendList.Clear(); - m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); - m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId); - m_ClientSendList.Remove(NetworkManager.ServerClientId); + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) + { + continue; + } + m_ClientSendList.Add(clientId); + } m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams); } @@ -1390,9 +1407,14 @@ internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerM InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); m_ClientSendList.Clear(); - m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); - m_ClientSendList.Remove(NetworkManager.ServerClientId); - + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + if (clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) + { + continue; + } + m_ClientSendList.Add(clientId); + } if (IsServerAuthoritative()) { m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams); diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 493b5b85b8..fe64a56431 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -400,6 +400,103 @@ public IEnumerator CrossFadeTransitionTests() AssertOnTimeout($"Timed out waiting for all clients to transition from synchronized cross fade!"); } + private bool AllTriggersDetectedOnObserversOnly(OwnerShipMode ownerShipMode, ulong nonObserverId) + { + if (ownerShipMode == OwnerShipMode.ClientOwner) + { + if (!TriggerTest.ClientsThatTriggered.Contains(m_ServerNetworkManager.LocalClientId)) + { + return false; + } + } + + foreach (var animatorTestHelper in AnimatorTestHelper.ClientSideInstances) + { + var currentClientId = animatorTestHelper.Value.NetworkManager.LocalClientId; + if (currentClientId == nonObserverId || (ownerShipMode == OwnerShipMode.ClientOwner && currentClientId == animatorTestHelper.Value.OwnerClientId)) + { + continue; + } + + if (!TriggerTest.ClientsThatTriggered.Contains(currentClientId)) + { + return false; + } + } + + // Should return false always + return !TriggerTest.ClientsThatTriggered.Contains(nonObserverId); + } + + private bool AllObserversSameLayerWeight(OwnerShipMode ownerShipMode, int layer, float targetWeight, ulong nonObserverId) + { + + if (ownerShipMode == OwnerShipMode.ClientOwner) + { + if (AnimatorTestHelper.ServerSideInstance.GetLayerWeight(layer) != targetWeight) + { + return false; + } + } + + foreach (var animatorTestHelper in AnimatorTestHelper.ClientSideInstances) + { + var currentClientId = animatorTestHelper.Value.NetworkManager.LocalClientId; + if (ownerShipMode == OwnerShipMode.ClientOwner && animatorTestHelper.Value.OwnerClientId == currentClientId) + { + continue; + } + if (currentClientId == nonObserverId) + { + if (animatorTestHelper.Value.GetLayerWeight(layer) == targetWeight) + { + return false; + } + } + else + if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight) + { + return false; + } + } + return true; + } + + [UnityTest] + public IEnumerator OnlyObserversAnimateTest() + { + // Spawn our test animator object + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); + var networkObject = objectInstance.GetComponent(); + // Wait for it to spawn server-side + var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); + + // Wait for it to spawn client-side + success = WaitForConditionOrTimeOutWithTimeTravel(WaitForClientsToInitialize); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); + + var animatorTestHelper = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + + networkObject.NetworkHide(m_ClientNetworkManagers[1].LocalClientId); + + yield return WaitForConditionOrTimeOut(() => !m_ClientNetworkManagers[1].SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId)); + AssertOnTimeout($"Client-{m_ClientNetworkManagers[1].LocalClientId} timed out waiting to hide {networkObject.name}!"); + + if (m_AuthoritativeMode == AuthoritativeMode.ServerAuth) + { + animatorTestHelper = AnimatorTestHelper.ServerSideInstance; + } + animatorTestHelper.SetTrigger(); + // Wait for all triggers to fire + yield return WaitForConditionOrTimeOut(() => AllTriggersDetectedOnObserversOnly(m_OwnerShipMode, m_ClientNetworkManagers[1].LocalClientId)); + AssertOnTimeout($"Timed out waiting for all triggers to match!"); + + animatorTestHelper.SetLayerWeight(1, 0.75f); + // Wait for all instances to update their weight value for layer 1 + success = WaitForConditionOrTimeOutWithTimeTravel(() => AllObserversSameLayerWeight(m_OwnerShipMode, 1, 0.75f, m_ClientNetworkManagers[1].LocalClientId)); + Assert.True(success, $"Timed out waiting for all instances to match weight 0.75 on layer 1!"); + } /// /// Verifies that triggers are synchronized with currently connected clients From da4649e43a0b54e0d235b693fd5df318b5c4baf3 Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:53:52 -0400 Subject: [PATCH 099/236] chore: NGO 2.0.0 merge back to develop NGO develop 2.0.0 (#3068) * bumping ngo version to 2.0.0 * adding unreleased header --- com.unity.netcode.gameobjects/CHANGELOG.md | 8 ++++++++ com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 274ed9e951..5b6d8f5049 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,14 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +### Fixed + +### Changed + +## [2.0.0] - 2024-09-12 + +### Added + - Added tooltips for all of the `NetworkObject` component's properties. (#3052) - Added message size validation to named and unnamed message sending functions for better error messages. (#3049) - Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3031) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index feb57a2538..1878824aa6 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0-pre.4", + "version": "2.0.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 3afc6f90435f3c61061ecbaeaac6a3b18cef73eb Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 17 Sep 2024 16:01:07 -0500 Subject: [PATCH 100/236] fix: spawn many objects apv failures (#3069) * update Minor update to some const values * test Adjusting test to handle slower CI VMs as well as logging additional information regarding the time it took to spawn vs the total time the test took. --- .../TestHelpers/Runtime/NetcodeIntegrationTest.cs | 7 ++++--- .../NetworkObjectSpawnManyObjectsTests.cs | 14 ++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 70ed7842f5..159d923d69 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -24,10 +24,11 @@ public abstract class NetcodeIntegrationTest /// Used to determine if a NetcodeIntegrationTest is currently running to /// determine how clients will load scenes /// + protected const float k_DefaultTimeoutPeriod = 8.0f; + protected const float k_TickFrequency = 1.0f / k_DefaultTickRate; internal static bool IsRunning { get; private set; } - - protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f); - protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate); + protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(k_DefaultTimeoutPeriod); + protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(k_TickFrequency); public NetcodeLogAssert NetcodeLogAssert; public enum SceneManagementState diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs index 7c8b1370e4..b80e055f0f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs @@ -12,8 +12,6 @@ namespace Unity.Netcode.RuntimeTests internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; - // "many" in this case means enough to exceed a ushort_max message size written in the header - // 1500 is not a magic number except that it's big enough to trigger a failure private const int k_SpawnedObjects = 1500; private NetworkPrefab m_PrefabToSpawn; @@ -52,19 +50,23 @@ protected override void OnServerAndClientsCreated() } [UnityTest] - // When this test fails it does so without an exception and will wait the default ~6 minutes - [Timeout(10000)] public IEnumerator WhenManyObjectsAreSpawnedAtOnce_AllAreReceived() { + var timeStarted = Time.realtimeSinceStartup; for (int x = 0; x < k_SpawnedObjects; x++) { NetworkObject serverObject = Object.Instantiate(m_PrefabToSpawn.Prefab).GetComponent(); serverObject.NetworkManagerOwner = m_ServerNetworkManager; serverObject.Spawn(); } + + var timeSpawned = Time.realtimeSinceStartup - timeStarted; + // Provide plenty of time to spawn all 1500 objects in case the CI VM is running slow + var timeoutHelper = new TimeoutHelper(30); // ensure all objects are replicated - yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects); - AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects!"); + yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects, timeoutHelper); + + AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects! Time to spawn: {timeSpawned} | Time to timeout: {timeStarted - Time.realtimeSinceStartup}", timeoutHelper); } } } From ef9e1c1309b6b38370dd2c022f95cf2d4bc113a2 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 25 Sep 2024 16:23:26 -0500 Subject: [PATCH 101/236] fix: client side disconnect incorrect client count on host-server side [MTTB-135] (Up-Port) (#3075) * fix Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. * test validation tests for the changes/updates to this pr. * update updating the changelog. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Connection/NetworkConnectionManager.cs | 42 ++++++++++++------- .../Runtime/ConnectionApprovalTimeoutTests.cs | 2 +- .../Tests/Runtime/DisconnectTests.cs | 3 ++ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 5b6d8f5049..fd680daedf 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075) + ### Changed ## [2.0.0] - 2024-09-12 diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index b5fff5404d..a77c91b6c4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -105,8 +105,12 @@ internal void InvokeOnClientConnectedCallback(ulong clientId) continue; } - peerClientIds[idx] = peerId; - ++idx; + // This assures if the server has not timed out prior to the client synchronizing that it doesn't exceed the allocated peer count. + if (peerClientIds.Length > idx) + { + peerClientIds[idx] = peerId; + ++idx; + } } try @@ -496,24 +500,32 @@ internal void DisconnectEventHandler(ulong transportClientId) // Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client. MessageManager.ProcessIncomingMessageQueue(); - InvokeOnClientDisconnectCallback(clientId); - - if (LocalClient.IsHost) - { - InvokeOnPeerDisconnectedCallback(clientId); - } - if (LocalClient.IsServer) { + // We need to process the disconnection before notifying OnClientDisconnectFromServer(clientId); + + // Now notify the client has disconnected + InvokeOnClientDisconnectCallback(clientId); + + if (LocalClient.IsHost) + { + InvokeOnPeerDisconnectedCallback(clientId); + } } - else // As long as we are not in the middle of a shutdown - if (!NetworkManager.ShutdownInProgress) + else { - // We must pass true here and not process any sends messages as we are no longer connected. - // Otherwise, attempting to process messages here can cause an exception within UnityTransport - // as the client ID is no longer valid. - NetworkManager.Shutdown(true); + // Notify local client of disconnection + InvokeOnClientDisconnectCallback(clientId); + + // As long as we are not in the middle of a shutdown + if (!NetworkManager.ShutdownInProgress) + { + // We must pass true here and not process any sends messages as we are no longer connected. + // Otherwise, attempting to process messages here can cause an exception within UnityTransport + // as the client ID is no longer valid. + NetworkManager.Shutdown(true); + } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_TransportDisconnect.End(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs index d324bad7ef..db1210face 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApprovalTimeoutTests.cs @@ -82,7 +82,7 @@ protected override IEnumerator OnStartedServerAndClients() public IEnumerator ValidateApprovalTimeout() { // Just delay for a second - yield return new WaitForSeconds(1); + yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.25f); // Verify we haven't received the time out message yet NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs index c4997fe9fd..af001655ae 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DisconnectTests.cs @@ -180,6 +180,9 @@ public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType client Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!"); Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ClientNetworkManagers[0]), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!"); Assert.IsTrue(m_DisconnectedEvent[m_ClientNetworkManagers[0]].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!"); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsIds.Count}!"); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClients.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClients.Count}!"); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsList.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsList.Count}!"); } if (m_OwnerPersistence == OwnerPersistence.DestroyWithOwner) From 390c332f05af4708eaf16ecabfc7a0a9745e8a86 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 27 Sep 2024 07:14:47 -0500 Subject: [PATCH 102/236] fix: player spawn not applying position and rotation via connection approval (#3078) * fix Apply the position and rotation set by the NetworkManager.ConnectionApprovalResponse when connection approval is enabled and auto-spawn player prefabs is enabled. * test Validation test for this PR * update adding changelog entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 4 +- .../Tests/Runtime/ConnectionApproval.cs | 41 +++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index fd680daedf..8817dfbe9c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index d3b2f9acd6..ce359c180a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -752,8 +752,8 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow } else { - // Create prefab instance - networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent(); + // Create prefab instance while applying any pre-assigned position and rotation values + networkObject = UnityEngine.Object.Instantiate(networkPrefabReference, position, rotation).GetComponent(); networkObject.NetworkManagerOwner = NetworkManager; networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs index 6fd311ffa9..efa19cffd5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ConnectionApproval.cs @@ -4,6 +4,7 @@ using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests @@ -12,9 +13,10 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(PlayerCreation.PrefabHash)] [TestFixture(PlayerCreation.NoPlayer)] [TestFixture(PlayerCreation.FailValidation)] - internal class ConnectionApprovalTests : NetcodeIntegrationTest + internal class ConnectionApprovalTests : IntegrationTestWithApproximation { private const string k_InvalidToken = "Invalid validation token!"; + public enum PlayerCreation { Prefab, @@ -24,6 +26,8 @@ public enum PlayerCreation } private PlayerCreation m_PlayerCreation; private bool m_ClientDisconnectReasonValidated; + private Vector3 m_ExpectedPosition; + private Quaternion m_ExpectedRotation; private Dictionary m_Validated = new Dictionary(); @@ -43,6 +47,12 @@ protected override bool ShouldCheckForSpawnedPlayers() protected override void OnServerAndClientsCreated() { + if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash) + { + m_ExpectedPosition = GetRandomVector3(-10.0f, 10.0f); + m_ExpectedRotation = Quaternion.Euler(GetRandomVector3(-359.98f, 359.98f)); + } + m_ClientDisconnectReasonValidated = false; m_BypassConnectionTimeout = m_PlayerCreation == PlayerCreation.FailValidation; m_Validated.Clear(); @@ -104,11 +114,36 @@ private bool ClientAndHostValidated() return true; } + private bool ValidatePlayersPositionRotation() + { + foreach (var playerEntries in m_PlayerNetworkObjects) + { + foreach (var player in playerEntries.Value) + { + if (!Approximately(player.Value.transform.position, m_ExpectedPosition)) + { + return false; + } + if (!Approximately(player.Value.transform.rotation, m_ExpectedRotation)) + { + return false; + } + } + } + return true; + } + [UnityTest] public IEnumerator ConnectionApproval() { yield return WaitForConditionOrTimeOut(ClientAndHostValidated); AssertOnTimeout("Timed out waiting for all clients to be approved!"); + + if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash) + { + yield return WaitForConditionOrTimeOut(ValidatePlayersPositionRotation); + AssertOnTimeout("Not all player prefabs spawned in the correct position and/or rotation!"); + } } private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) @@ -127,8 +162,8 @@ private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.Conn } response.CreatePlayerObject = ShouldCheckForSpawnedPlayers(); - response.Position = null; - response.Rotation = null; + response.Position = m_ExpectedPosition; + response.Rotation = m_ExpectedRotation; response.PlayerPrefabHash = m_PlayerCreation == PlayerCreation.PrefabHash ? m_PlayerPrefab.GetComponent().GlobalObjectIdHash : null; } From cb7ec98cccc462f8d131ba3995e17f84fcd59bc2 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 27 Sep 2024 09:23:10 -0500 Subject: [PATCH 103/236] fix: player prefab spawning not honoring when spawnwithobservers is disabled (#3077) * fix When SpawnWithObservers is disabled on player prefab, the instantiated instance should only spawn on the authority side. For client-server this will always be the server/host. For distributed authority this will always be the owner. * test Validation test for the SpawnWithObservers regression bug. * update updating changelog entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkObject.cs | 18 +++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 15 +++- .../Runtime/NetcodeIntegrationTest.cs | 7 +- .../Tests/Runtime/PlayerObjectTests.cs | 70 ++++++++++++++++++- 5 files changed, 103 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8817dfbe9c..1089c4012f 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) +- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) - Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index f89bc1d345..5cca18a66a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1613,7 +1613,12 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) { - NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this); + // If spawning with observers or if not spawning with observers but the observer count is greater than 1 (i.e. owner/authority creating), + // then we want to send a spawn notification. + if (SpawnWithObservers || !SpawnWithObservers && Observers.Count > 1) + { + NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this); + } } else { @@ -3053,10 +3058,15 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf } } - // Add all known players to the observers list if they don't already exist - foreach (var player in networkManager.SpawnManager.PlayerObjects) + // Only add all other players as observers if we are spawning with observers, + // otherwise user controls via NetworkShow. + if (networkObject.SpawnWithObservers) { - networkObject.Observers.Add(player.OwnerClientId); + // Add all known players to the observers list if they don't already exist + foreach (var player in networkManager.SpawnManager.PlayerObjects) + { + networkObject.Observers.Add(player.OwnerClientId); + } } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index ce359c180a..06b5b6c364 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -72,11 +72,22 @@ private void AddPlayerObject(NetworkObject playerObject) return; } } + foreach (var player in m_PlayerObjects) { - player.Observers.Add(playerObject.OwnerClientId); - playerObject.Observers.Add(player.OwnerClientId); + // If the player's SpawnWithObservers is not set then do not add the new player object's owner as an observer. + if (player.SpawnWithObservers) + { + player.Observers.Add(playerObject.OwnerClientId); + } + + // If the new player object's SpawnWithObservers is not set then do not add this player as an observer to the new player object. + if (playerObject.SpawnWithObservers) + { + playerObject.Observers.Add(player.OwnerClientId); + } } + m_PlayerObjects.Add(playerObject); if (!m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId)) { diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 159d923d69..a84a7ccb7c 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -545,9 +545,14 @@ protected IEnumerator CreateAndStartNewClient() private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient) { m_InternalErrorLog.Clear(); + // If we are not checking for spawned players then exit early with a success + if (!ShouldCheckForSpawnedPlayers()) + { + return true; + } + // Continue to populate the PlayerObjects list until all player object (local and clone) are found ClientNetworkManagerPostStart(joinedClient); - var playerObjectRelative = m_ServerNetworkManager.SpawnManager.PlayerObjects.Where((c) => c.OwnerClientId == joinedClient.LocalClientId).FirstOrDefault(); if (playerObjectRelative == null) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs index 8fba758de9..dc75f41337 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Server)] internal class PlayerObjectTests : NetcodeIntegrationTest { - protected override int NumberOfClients => 1; + protected override int NumberOfClients => 2; protected GameObject m_NewPlayerToSpawn; @@ -52,4 +52,72 @@ public IEnumerator SpawnAndReplaceExistingPlayerObject() Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side player object to change!"); } } + + /// + /// Validate that when auto-player spawning but SpawnWithObservers is disabled, + /// the player instantiated is only spawned on the authority side. + /// + [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class PlayerSpawnNoObserversTest : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + public PlayerSpawnNoObserversTest(HostOrServer hostOrServer) : base(hostOrServer) { } + + protected override bool ShouldCheckForSpawnedPlayers() + { + return false; + } + + protected override void OnCreatePlayerPrefab() + { + var playerNetworkObject = m_PlayerPrefab.GetComponent(); + playerNetworkObject.SpawnWithObservers = false; + base.OnCreatePlayerPrefab(); + } + + [UnityTest] + public IEnumerator SpawnWithNoObservers() + { + yield return s_DefaultWaitForTick; + + if (!m_DistributedAuthority) + { + // Make sure clients did not spawn their player object on any of the clients including the owner. + foreach (var client in m_ClientNetworkManagers) + { + foreach (var playerObject in m_ServerNetworkManager.SpawnManager.PlayerObjects) + { + Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObject.NetworkObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{playerObject.NetworkObjectId}!"); + } + } + } + else + { + // For distributed authority, we want to make sure the player object is only spawned on the authority side and all non-authority instances did not spawn it. + var playerObjectId = m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId; + foreach (var client in m_ClientNetworkManagers) + { + Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{m_ServerNetworkManager.LocalClientId}!"); + } + + foreach (var clientPlayer in m_ClientNetworkManagers) + { + playerObjectId = clientPlayer.LocalClient.PlayerObject.NetworkObjectId; + Assert.IsFalse(m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{m_ServerNetworkManager.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!"); + foreach (var client in m_ClientNetworkManagers) + { + if (clientPlayer == client) + { + continue; + } + Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!"); + } + } + + } + } + } } From 87d23e942434d22240df5a594366d4f64b4e908b Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 8 Oct 2024 17:52:08 -0500 Subject: [PATCH 104/236] fix: NetworkManager instantiate & destroy notifications and player spawn prefab offset (#3088) * update Adding OnInstantiated and OnDestroying events to NetworkManager in order to provide the ability to know when a new NetworkManager instance has been instantiated and is being destroyed. * update Fixing an issue where a player would not spawn with its default network prefab instance's position and rotation settings. * test Adding validation test * update adding changelog entries * style Adding CR between Added and entries. * fix Fixing player spawn position for client-server. * test The validation test to validate the player's spawn position will be the prefab's offset if no other values are specified. * style removing using directive not needed. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 + .../Connection/NetworkConnectionManager.cs | 8 +-- .../Runtime/Core/NetworkManager.cs | 16 +++++ .../Runtime/Spawning/NetworkSpawnManager.cs | 8 ++- .../Runtime/NetworkManagerEventsTests.cs | 54 ++++++++++++++++ .../Tests/Runtime/PlayerObjectTests.cs | 64 +++++++++++++++++++ 6 files changed, 146 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 1089c4012f..a9af9e9072 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088) +- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088) + ### Fixed - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index a77c91b6c4..b02248009f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -751,8 +751,8 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne // Server-side spawning (only if there is a prefab hash or player prefab provided) if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null)) { - var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault()) - : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault()); + var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position ?? null, response.Rotation ?? null) + : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, response.Position ?? null, response.Rotation ?? null); // Spawn the player NetworkObject locally NetworkManager.SpawnManager.SpawnNetworkObjectLocally( @@ -896,7 +896,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne /// /// Client-Side Spawning in distributed authority mode uses this to spawn the player. /// - internal void CreateAndSpawnPlayer(ulong ownerId, Vector3 position = default, Quaternion rotation = default) + internal void CreateAndSpawnPlayer(ulong ownerId) { if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide) { @@ -904,7 +904,7 @@ internal void CreateAndSpawnPlayer(ulong ownerId, Vector3 position = default, Qu if (playerPrefab != null) { var globalObjectIdHash = playerPrefab.GetComponent().GlobalObjectIdHash; - var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, position, rotation); + var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation); networkObject.IsSceneObject = false; networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 0cdb995128..28ad916f55 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -17,6 +17,17 @@ namespace Unity.Netcode [AddComponentMenu("Netcode/Network Manager", -100)] public class NetworkManager : MonoBehaviour, INetworkUpdateSystem { + /// + /// Subscribe to this static event to get notifications when a instance has been instantiated. + /// + public static event Action OnInstantiated; + + /// + /// Subscribe to this static event to get notifications when a instance is being destroyed. + /// + public static event Action OnDestroying; + + #if UNITY_EDITOR // Inspector view expand/collapse settings for this derived child class [HideInInspector] @@ -1030,6 +1041,8 @@ private void Awake() #if UNITY_EDITOR EditorApplication.playModeStateChanged += ModeChanged; #endif + // Notify we have instantiated a new instance of NetworkManager. + OnInstantiated?.Invoke(this); } private void OnEnable() @@ -1632,6 +1645,9 @@ private void OnDestroy() UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded; + // Notify we are destroying NetworkManager + OnDestroying?.Invoke(this); + if (Singleton == this) { Singleton = null; diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 06b5b6c364..daac9dea21 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -706,14 +706,14 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the /// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned. /// - internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3 position = default, Quaternion rotation = default, bool isScenePlaced = false) + internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false) { NetworkObject networkObject = null; // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash)) { // Let the handler spawn the NetworkObject - networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position, rotation); + networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default); networkObject.NetworkManagerOwner = NetworkManager; } else @@ -764,7 +764,9 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow else { // Create prefab instance while applying any pre-assigned position and rotation values - networkObject = UnityEngine.Object.Instantiate(networkPrefabReference, position, rotation).GetComponent(); + networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent(); + networkObject.transform.position = position ?? networkObject.transform.position; + networkObject.transform.rotation = rotation ?? networkObject.transform.rotation; networkObject.NetworkManagerOwner = NetworkManager; networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index 195e8b3ea5..8b1c993268 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -13,6 +13,60 @@ internal class NetworkManagerEventsTests private NetworkManager m_ClientManager; private NetworkManager m_ServerManager; + private NetworkManager m_NetworkManagerInstantiated; + private bool m_Instantiated; + private bool m_Destroyed; + + /// + /// Validates the and event notifications + /// + [UnityTest] + public IEnumerator InstantiatedAndDestroyingNotifications() + { + NetworkManager.OnInstantiated += NetworkManager_OnInstantiated; + NetworkManager.OnDestroying += NetworkManager_OnDestroying; + var waitPeriod = new WaitForSeconds(0.01f); + var prefab = new GameObject("InstantiateDestroy"); + var networkManagerPrefab = prefab.AddComponent(); + + Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} prefab did not get instantiated event notification!"); + Assert.IsTrue(m_NetworkManagerInstantiated == networkManagerPrefab, $"{nameof(NetworkManager)} prefab parameter did not match!"); + + m_Instantiated = false; + m_NetworkManagerInstantiated = null; + + for (int i = 0; i < 3; i++) + { + var instance = Object.Instantiate(prefab); + var networkManager = instance.GetComponent(); + Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} instance-{i} did not get instantiated event notification!"); + Assert.IsTrue(m_NetworkManagerInstantiated == networkManager, $"{nameof(NetworkManager)} instance-{i} parameter did not match!"); + Object.DestroyImmediate(instance); + Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} instance-{i} did not get destroying event notification!"); + m_Instantiated = false; + m_NetworkManagerInstantiated = null; + m_Destroyed = false; + } + m_NetworkManagerInstantiated = networkManagerPrefab; + Object.Destroy(prefab); + yield return null; + Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} prefab did not get destroying event notification!"); + NetworkManager.OnInstantiated -= NetworkManager_OnInstantiated; + NetworkManager.OnDestroying -= NetworkManager_OnDestroying; + } + + private void NetworkManager_OnInstantiated(NetworkManager networkManager) + { + m_Instantiated = true; + m_NetworkManagerInstantiated = networkManager; + } + + private void NetworkManager_OnDestroying(NetworkManager networkManager) + { + m_Destroyed = true; + Assert.True(m_NetworkManagerInstantiated == networkManager, $"Destroying {nameof(NetworkManager)} and current instance is not a match for the one passed into the event!"); + } + [UnityTest] public IEnumerator OnServerStoppedCalledWhenServerStops() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs index dc75f41337..e382bcfa88 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs @@ -120,4 +120,68 @@ public IEnumerator SpawnWithNoObservers() } } } + + /// + /// This test validates the player position and rotation is correct + /// relative to the prefab's initial settings if no changes are applied. + /// + [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class PlayerSpawnPositionTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 2; + + public PlayerSpawnPositionTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + private Vector3 m_PlayerPosition; + private Quaternion m_PlayerRotation; + + protected override void OnCreatePlayerPrefab() + { + var playerNetworkObject = m_PlayerPrefab.GetComponent(); + m_PlayerPosition = GetRandomVector3(-10.0f, 10.0f); + m_PlayerRotation = Quaternion.Euler(GetRandomVector3(-180.0f, 180.0f)); + playerNetworkObject.transform.position = m_PlayerPosition; + playerNetworkObject.transform.rotation = m_PlayerRotation; + base.OnCreatePlayerPrefab(); + } + + private void PlayerTransformMatches(NetworkObject player) + { + var position = player.transform.position; + var rotation = player.transform.rotation; + Assert.True(Approximately(m_PlayerPosition, position), $"Client-{player.OwnerClientId} position {position} does not match the prefab position {m_PlayerPosition}!"); + Assert.True(Approximately(m_PlayerRotation, rotation), $"Client-{player.OwnerClientId} rotation {rotation.eulerAngles} does not match the prefab rotation {m_PlayerRotation.eulerAngles}!"); + } + + [UnityTest] + public IEnumerator PlayerSpawnPosition() + { + if (m_ServerNetworkManager.IsHost) + { + PlayerTransformMatches(m_ServerNetworkManager.LocalClient.PlayerObject); + + foreach (var client in m_ClientNetworkManagers) + { + yield return WaitForConditionOrTimeOut(() => client.SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId)); + AssertOnTimeout($"Client-{client.LocalClientId} does not contain a player prefab instance for client-{m_ServerNetworkManager.LocalClientId}!"); + PlayerTransformMatches(client.SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId]); + } + } + + foreach (var client in m_ClientNetworkManagers) + { + yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId)); + AssertOnTimeout($"Client-{m_ServerNetworkManager.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!"); + PlayerTransformMatches(m_ServerNetworkManager.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]); + foreach (var subClient in m_ClientNetworkManagers) + { + yield return WaitForConditionOrTimeOut(() => subClient.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId)); + AssertOnTimeout($"Client-{subClient.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!"); + PlayerTransformMatches(subClient.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]); + } + } + } + } } From 0bef65cf5ff96c2d224caafcb6cdb9a825bf9695 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 10 Oct 2024 11:48:50 -0500 Subject: [PATCH 105/236] chore: updates to comb-server bypass and connection sequence configurations (#3093) * update remove the magic injected number from NT. * update remove magic * update Adding NGO version detection and CMB service development modification capabilities. Updating ConnectionRequestMessage and ConnectionApprovedMessage to use INetworkSerializable structs as a means to communicating a clients configuration and NGO version as well as to receive a configuration from the comb-server. * update Removing logging version number from console when CMB_SERVICE_DEVELOPMENT is not defined. * Add version to service config * update Minor adjustments to handle DAHost mode and to process connection approved message from service. --------- Co-authored-by: EmandM --- .../Editor/NetworkManagerEditor.cs | 26 +++++- .../Runtime/Components/NetworkTransform.cs | 10 --- .../Connection/NetworkConnectionManager.cs | 10 ++- .../Runtime/Core/NetworkManager.cs | 51 +++++++++++ .../Messages/ConnectionApprovedMessage.cs | 85 +++++++++++++++++-- .../Messages/ConnectionRequestMessage.cs | 51 +++++++++-- 6 files changed, 203 insertions(+), 30 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 5445b0d8e8..b93e7d90d5 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -48,6 +48,12 @@ public class NetworkManagerEditor : NetcodeEditorBase private SerializedProperty m_NetworkProfileMetrics; private SerializedProperty m_NetworkMessageMetrics; +#if CMB_SERVICE_DEVELOPMENT + private SerializedProperty m_MajorVersion; + private SerializedProperty m_MinorVersion; + private SerializedProperty m_PatchVersion; +#endif + private NetworkManager m_NetworkManager; private bool m_Initialized; @@ -120,8 +126,11 @@ private void Initialize() #if MULTIPLAYER_TOOLS m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics"); #endif - - +#if CMB_SERVICE_DEVELOPMENT + m_MajorVersion = serializedObject.FindProperty(nameof(NetworkManager.MajorVersion)); + m_MinorVersion = serializedObject.FindProperty(nameof(NetworkManager.MinorVersion)); + m_PatchVersion = serializedObject.FindProperty(nameof(NetworkManager.PatchVersion)); +#endif m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty .FindPropertyRelative(nameof(NetworkConfig.Prefabs)) @@ -161,6 +170,11 @@ private void CheckNullProperties() #if MULTIPLAYER_TOOLS m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics"); #endif +#if CMB_SERVICE_DEVELOPMENT + m_MajorVersion = serializedObject.FindProperty(nameof(NetworkManager.MajorVersion)); + m_MinorVersion = serializedObject.FindProperty(nameof(NetworkManager.MinorVersion)); + m_PatchVersion = serializedObject.FindProperty(nameof(NetworkManager.PatchVersion)); +#endif m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty @@ -173,10 +187,18 @@ private void DisplayNetworkManagerProperties() if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) { serializedObject.Update(); + EditorGUILayout.PropertyField(m_RunInBackgroundProperty); EditorGUILayout.PropertyField(m_LogLevelProperty); + EditorGUILayout.Space(); +#if CMB_SERVICE_DEVELOPMENT + EditorGUILayout.LabelField("Version:", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(m_MajorVersion); + EditorGUILayout.PropertyField(m_MinorVersion); + EditorGUILayout.PropertyField(m_PatchVersion); EditorGUILayout.Space(); +#endif EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel); #if MULTIPLAYER_SERVICES_SDK_INSTALLED EditorGUILayout.PropertyField(m_NetworkTopologyProperty); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index b9d8eabab3..d4d1f93a98 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1463,8 +1463,6 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) // For test logging purposes internal NetworkTransformState SynchronizeState; - // DANGO-TODO: We will want to remove this when we migrate NetworkTransforms to a dedicated internal message - private const ushort k_NetworkTransformStateMagic = 0xf48d; #endregion #region ONSYNCHRONIZE @@ -1494,14 +1492,6 @@ protected override void OnSynchronize(ref BufferSerializer serializer) if (serializer.IsWriter) { - // DANGO-TODO: This magic value is sent to the server in order to identify the network transform. - // The server discards it before forwarding synchronization data to other clients. - if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) - { - var writer = serializer.GetFastBufferWriter(); - writer.WriteValueSafe(k_NetworkTransformStateMagic); - } - SynchronizeState.IsTeleportingNextFrame = true; var transformToCommit = transform; // If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index b02248009f..33098f36c2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -564,9 +564,6 @@ private void SendConnectionRequest() var message = new ConnectionRequestMessage { CMBServiceConnection = NetworkManager.CMBServiceConnection, - TickRate = NetworkManager.NetworkConfig.TickRate, - EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement, - // Since only a remote client will send a connection request, we should always force the rebuilding of the NetworkConfig hash value ConfigHash = NetworkManager.NetworkConfig.GetConfig(false), ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval, @@ -574,6 +571,13 @@ private void SendConnectionRequest() MessageVersions = new NativeArray(MessageManager.MessageHandlers.Length, Allocator.Temp) }; + if (NetworkManager.CMBServiceConnection) + { + message.ClientConfig.NGOVersion = NetworkManager.GetNGOVersion(); + message.ClientConfig.TickRate = NetworkManager.NetworkConfig.TickRate; + message.ClientConfig.EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement; + } + for (int index = 0; index < MessageManager.MessageHandlers.Length; index++) { if (MessageManager.MessageTypes[index] != null) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 28ad916f55..0bb73b3a37 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -5,6 +5,7 @@ using UnityEngine; #if UNITY_EDITOR using UnityEditor; +using PackageInfo = UnityEditor.PackageManager.PackageInfo; #endif using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; @@ -885,6 +886,30 @@ internal T Value internal Override PortOverride; + + [HideInInspector] + [SerializeField] + [Range(0, 255)] + internal byte MajorVersion; + [HideInInspector] + [SerializeField] + [Range(0, 255)] + internal byte MinorVersion; + [HideInInspector] + [SerializeField] + [Range(0, 255)] + internal byte PatchVersion; + + internal NGOVersion GetNGOVersion() + { + return new NGOVersion() + { + Major = MajorVersion, + Minor = MinorVersion, + Patch = PatchVersion + }; + } + #if UNITY_EDITOR internal static INetworkManagerHelper NetworkManagerHelper; @@ -911,6 +936,26 @@ protected virtual void OnValidateComponent() } + private PackageInfo GetPackageInfo(string packageName) + { + return AssetDatabase.FindAssets("package").Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath(x) != null).Select(PackageInfo.FindForAssetPath).Where(x => x != null).First(x => x.name == packageName); + } + + private void SetPackageVersion() + { + var packageInfo = GetPackageInfo("com.unity.netcode.gameobjects"); + if (packageInfo != null) + { + var versionSplit = packageInfo.version.Split("."); + if (versionSplit.Length == 3) + { + MajorVersion = byte.Parse(versionSplit[0]); + MinorVersion = byte.Parse(versionSplit[1]); + PatchVersion = byte.Parse(versionSplit[2]); + } + } + } + internal void OnValidate() { if (NetworkConfig == null) @@ -918,6 +963,12 @@ internal void OnValidate() return; // May occur when the component is added } +#if !CMB_SERVICE_DEVELOPMENT + SetPackageVersion(); +#else + Debug.Log($"Major:({MajorVersion}) Minor({MinorVersion}) Patch({PatchVersion})"); +#endif + if (GetComponentInChildren() != null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index bb3e446817..7b9a87fe9c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -3,14 +3,39 @@ namespace Unity.Netcode { + internal struct ServiceConfig : INetworkSerializable + { + public uint Version; + public bool IsRestoredSession; + public ulong CurrentSessionOwner; + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsWriter) + { + BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), Version); + serializer.SerializeValue(ref IsRestoredSession); + BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), CurrentSessionOwner); + } + else + { + ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out Version); + serializer.SerializeValue(ref IsRestoredSession); + ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out CurrentSessionOwner); + } + } + } + internal struct ConnectionApprovedMessage : INetworkMessage { + private const int k_AddCMBServiceConfig = 2; private const int k_VersionAddClientIds = 1; - public int Version => k_VersionAddClientIds; + public int Version => k_AddCMBServiceConfig; public ulong OwnerClientId; public int NetworkTick; // The cloud state service should set this if we are restoring a session + public ServiceConfig ServiceConfig; public bool IsRestoredSession; public ulong CurrentSessionOwner; // Not serialized @@ -25,6 +50,32 @@ internal struct ConnectionApprovedMessage : INetworkMessage public NativeArray ConnectedClientIds; + private int m_ReceiveMessageVersion; + + private ulong GetSessionOwner() + { + if (m_ReceiveMessageVersion >= k_AddCMBServiceConfig) + { + return ServiceConfig.CurrentSessionOwner; + } + else + { + return CurrentSessionOwner; + } + } + + private bool GetIsSessionRestor() + { + if (m_ReceiveMessageVersion >= k_AddCMBServiceConfig) + { + return ServiceConfig.IsRestoredSession; + } + else + { + return IsRestoredSession; + } + } + public void Serialize(FastBufferWriter writer, int targetVersion) { // ============================================================ @@ -45,8 +96,17 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, NetworkTick); if (IsDistributedAuthority) { - writer.WriteValueSafe(IsRestoredSession); - BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner); + if (targetVersion >= k_AddCMBServiceConfig) + { + ServiceConfig.IsRestoredSession = false; + ServiceConfig.CurrentSessionOwner = CurrentSessionOwner; + writer.WriteNetworkSerializable(ServiceConfig); + } + else + { + writer.WriteValueSafe(IsRestoredSession); + BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner); + } } if (targetVersion >= k_VersionAddClientIds) @@ -122,13 +182,20 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int // ============================================================ // END FORBIDDEN SEGMENT // ============================================================ - + m_ReceiveMessageVersion = receivedMessageVersion; ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick); if (networkManager.DistributedAuthorityMode) { - reader.ReadValueSafe(out IsRestoredSession); - ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner); + if (receivedMessageVersion >= k_AddCMBServiceConfig) + { + reader.ReadNetworkSerializable(out ServiceConfig); + } + else + { + reader.ReadValueSafe(out IsRestoredSession); + ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner); + } } if (receivedMessageVersion >= k_VersionAddClientIds) @@ -157,7 +224,7 @@ public void Handle(ref NetworkContext context) if (networkManager.DistributedAuthorityMode) { - networkManager.SetSessionOwner(CurrentSessionOwner); + networkManager.SetSessionOwner(GetSessionOwner()); if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement) { networkManager.SceneManager.InitializeScenesLoaded(); @@ -233,9 +300,9 @@ public void Handle(ref NetworkContext context) // Mark the client being connected networkManager.IsConnectedClient = true; - networkManager.SceneManager.IsRestoringSession = IsRestoredSession; + networkManager.SceneManager.IsRestoringSession = GetIsSessionRestor(); - if (!IsRestoredSession) + if (!networkManager.SceneManager.IsRestoringSession) { // Synchronize the service with the initial session owner's loaded scenes and spawned objects networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index 790791dd24..87d49cb332 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -2,16 +2,56 @@ namespace Unity.Netcode { - internal struct ConnectionRequestMessage : INetworkMessage + internal struct NGOVersion : INetworkSerializable { - public int Version => 0; + public byte Major; + public byte Minor; + public byte Patch; - public ulong ConfigHash; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref Major); + serializer.SerializeValue(ref Minor); + serializer.SerializeValue(ref Patch); + } + } - public bool CMBServiceConnection; + internal struct ClientConfig : INetworkSerializable + { + public NGOVersion NGOVersion; public uint TickRate; public bool EnableSceneManagement; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeNetworkSerializable(ref NGOVersion); + if (serializer.IsWriter) + { + var writer = serializer.GetFastBufferWriter(); + writer.WriteValueSafe(TickRate); + writer.WriteValueSafe(EnableSceneManagement); + } + else + { + var reader = serializer.GetFastBufferReader(); + reader.ReadValueSafe(out TickRate); + reader.ReadValueSafe(out EnableSceneManagement); + } + } + } + + internal struct ConnectionRequestMessage : INetworkMessage + { + // This version update is unidirectional (client to service) and version + // handling occurs on the service side. This serialized data is never sent + // to a host or server. + private const int k_SendClientConfigToService = 1; + public int Version => k_SendClientConfigToService; + + public ulong ConfigHash; + public bool CMBServiceConnection; + public ClientConfig ClientConfig; + public byte[] ConnectionData; public bool ShouldSendConnectionData; @@ -36,8 +76,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (CMBServiceConnection) { - writer.WriteValueSafe(TickRate); - writer.WriteValueSafe(EnableSceneManagement); + writer.WriteNetworkSerializable(ClientConfig); } if (ShouldSendConnectionData) From 058481b64dac31c2cd27b54ecc54f6b6de550a4e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 10 Oct 2024 13:51:12 -0500 Subject: [PATCH 106/236] fix: in-sceneobject NetworkObject update in editor tool (up-port) (#3092) * update Update to NetworkObject to assist with the refresh in-scene prefab instances * fix Fixing issue where updating a prefab used as in-scene placed prefab instance(s) would not properly get updated if the root prefab asset was updated to have a NetworkObject component. * update changelog entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkObject.cs | 10 +-- .../Runtime/Core/NetworkObjectRefreshTool.cs | 72 +++++++++++++++---- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a9af9e9072..2ed4484e52 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) - Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 5cca18a66a..9cd176c39b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -113,11 +113,6 @@ internal void RefreshAllPrefabInstances() } // Handle updating the currently active scene - var networkObjects = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); - foreach (var networkObject in networkObjects) - { - networkObject.OnValidate(); - } NetworkObjectRefreshTool.ProcessActiveScene(); // Refresh all build settings scenes @@ -130,14 +125,14 @@ internal void RefreshAllPrefabInstances() continue; } // Add the scene to be processed - NetworkObjectRefreshTool.ProcessScene(editorScene.path, false); + NetworkObjectRefreshTool.ProcessScene(editorScene.path, true); } // Process all added scenes NetworkObjectRefreshTool.ProcessScenes(); } - private void OnValidate() + internal void OnValidate() { // do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name)) @@ -229,6 +224,7 @@ private void CheckForInScenePlaced() if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash) { InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash; + EditorUtility.SetDirty(this); } IsSceneObject = true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs index b9c6db0cf9..63d48e914d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; @@ -21,6 +23,28 @@ internal class NetworkObjectRefreshTool internal static Action AllScenesProcessed; + internal static NetworkObject PrefabNetworkObject; + + internal static void LogInfo(string msg, bool append = false) + { + if (!append) + { + s_Log.AppendLine(msg); + } + else + { + s_Log.Append(msg); + } + } + + internal static void FlushLog() + { + Debug.Log(s_Log.ToString()); + s_Log.Clear(); + } + + private static StringBuilder s_Log = new StringBuilder(); + internal static void ProcessScene(string scenePath, bool processScenes = true) { if (!s_ScenesToUpdate.Contains(scenePath)) @@ -29,7 +53,10 @@ internal static void ProcessScene(string scenePath, bool processScenes = true) { EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened; EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved; + s_Log.Clear(); + LogInfo("NetworkObject Refresh Scenes to Process:"); } + LogInfo($"[{scenePath}]", true); s_ScenesToUpdate.Add(scenePath); } s_ProcessScenes = processScenes; @@ -37,6 +64,7 @@ internal static void ProcessScene(string scenePath, bool processScenes = true) internal static void ProcessActiveScene() { + FlushLog(); var activeScene = SceneManager.GetActiveScene(); if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes) { @@ -54,10 +82,12 @@ internal static void ProcessScenes() } else { + s_ProcessScenes = false; s_CloseScenes = false; EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved; EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened; AllScenesProcessed?.Invoke(); + FlushLog(); } } @@ -68,9 +98,8 @@ private static void FinishedProcessingScene(Scene scene, bool refreshed = false) // Provide a log of all scenes that were modified to the user if (refreshed) { - Debug.Log($"Refreshed and saved updates to scene: {scene.name}"); + LogInfo($"Refreshed and saved updates to scene: {scene.name}"); } - s_ProcessScenes = false; s_ScenesToUpdate.Remove(scene.path); if (scene != SceneManager.GetActiveScene()) @@ -88,24 +117,41 @@ private static void EditorSceneManager_sceneSaved(Scene scene) private static void SceneOpened(Scene scene) { + LogInfo($"Processing scene {scene.name}:"); if (s_ScenesToUpdate.Contains(scene.path)) { if (s_ProcessScenes) { - if (!EditorSceneManager.MarkSceneDirty(scene)) - { - Debug.Log($"Scene {scene.name} did not get marked as dirty!"); - FinishedProcessingScene(scene); - } - else + var prefabInstances = PrefabUtility.FindAllInstancesOfPrefab(PrefabNetworkObject.gameObject); + + if (prefabInstances.Length > 0) { - EditorSceneManager.SaveScene(scene); + var instancesSceneLoadedSpecific = prefabInstances.Where((c) => c.scene == scene).ToList(); + + if (instancesSceneLoadedSpecific.Count > 0) + { + foreach (var prefabInstance in instancesSceneLoadedSpecific) + { + prefabInstance.GetComponent().OnValidate(); + } + + if (!EditorSceneManager.MarkSceneDirty(scene)) + { + LogInfo($"Scene {scene.name} did not get marked as dirty!"); + FinishedProcessingScene(scene); + } + else + { + LogInfo($"Changes detected and applied!"); + EditorSceneManager.SaveScene(scene); + } + return; + } } } - else - { - FinishedProcessingScene(scene); - } + + LogInfo($"No changes required."); + FinishedProcessingScene(scene); } } From 0055e1c9504a80b9c295f81009ee8d17b2103147 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 11 Oct 2024 07:36:38 -0500 Subject: [PATCH 107/236] fix: provide non Rigidbody contact events and Rigidbody prioritization (#3094) * fix Provide an extended IContactEventHandlerWithInfo that allows users to prioritize which object is being collided with as well as being able to determine if the instance should return non-rigidbody contact events. * update Adding changelog entry. * update Adding associated PR to the entry * style Adding XML API documentation. * fix Assuring the default non-info contact event handler sets priority to the non-kinematic bodies. * test Added validation test for RigidbodyContactEventManager --- com.unity.netcode.gameobjects/CHANGELOG.md | 5 +- .../RigidbodyContactEventManager.cs | 168 +++++++- .../Runtime/Physics/NetworkRigidbodyTest.cs | 383 ++++++++++++++++++ 3 files changed, 549 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2ed4484e52..32fea4c828 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,7 +9,10 @@ Additional documentation and release notes are available at [Multiplayer Documen [Unreleased] ### Added - + +- Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094) + - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094) + - `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094) - Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088) - Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 9471a94fb5..d0808d2886 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -6,13 +6,70 @@ namespace Unity.Netcode.Components { + /// + /// Information a returns to via
+ /// if the registers itself with as opposed to . + ///
+ public struct ContactEventHandlerInfo + { + /// + /// When set to true, the will include non-Rigidbody based contact events.
+ /// When the invokes the it will return null in place
+ /// of the collidingBody parameter if the contact event occurred with a collider that is not registered with the . + ///
+ public bool ProvideNonRigidBodyContactEvents; + /// + /// When set to true, the will prioritize invoking

+ /// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a is owned by the local client
+ /// will assure is only invoked on the authoritative side. + ///
+ public bool HasContactEventPriority; + } + + /// + /// Default implementation required to register a with a instance. + /// + /// + /// Recommended to implement this method on a component + /// public interface IContactEventHandler { + /// + /// Should return a . + /// Rigidbody GetRigidbody(); + /// + /// Invoked by the instance. + /// + /// A unique contact event identifier. + /// The average normal of the collision between two colliders. + /// If not null, this will be a registered that was part of the collision contact event. + /// The world space location of the contact event. + /// Will be set if this is a collision stay contact event (i.e. it is not the first contact event and continually has contact) + /// The average normal of the collision stay contact over time. void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default); } + /// + /// This is an extended version of and can be used to register a with a instance.
+ /// This provides additional information to the for each set of contact events it is processing. + ///
+ public interface IContactEventHandlerWithInfo : IContactEventHandler + { + /// + /// Invoked by for each set of contact events it is processing (prior to processing). + /// + /// + ContactEventHandlerInfo GetContactEventHandlerInfo(); + } + + /// + /// Add this component to an in-scene placed GameObject to provide faster collision event processing between instances and optionally static colliders. + ///
+ ///
+ ///
+ ///
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")] public class RigidbodyContactEventManager : MonoBehaviour { @@ -34,6 +91,7 @@ private struct JobResultStruct private readonly Dictionary m_RigidbodyMapping = new Dictionary(); private readonly Dictionary m_HandlerMapping = new Dictionary(); + private readonly Dictionary m_HandlerInfo = new Dictionary(); private void OnEnable() { @@ -49,6 +107,15 @@ private void OnEnable() Instance = this; } + /// + /// Any implementation can register a to be handled by this instance. + /// + /// + /// You should enable for each associated with the being registered.
+ /// You can enable this during run time or within the editor's inspector view. + ///
+ /// or + /// true to register and false to remove from being registered public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true) { var rigidbody = contactEventHandler.GetRigidbody(); @@ -64,6 +131,22 @@ public void RegisterHandler(IContactEventHandler contactEventHandler, bool regis { m_HandlerMapping.Add(instanceId, contactEventHandler); } + + if (!m_HandlerInfo.ContainsKey(instanceId)) + { + var handlerInfo = new ContactEventHandlerInfo() + { + HasContactEventPriority = true, + ProvideNonRigidBodyContactEvents = false, + }; + var handlerWithInfo = contactEventHandler as IContactEventHandlerWithInfo; + + if (handlerWithInfo != null) + { + handlerInfo = handlerWithInfo.GetContactEventHandlerInfo(); + } + m_HandlerInfo.Add(instanceId, handlerInfo); + } } else { @@ -88,25 +171,98 @@ private void OnDisable() private void ProcessCollisions() { + foreach (var contactEventHandler in m_HandlerMapping) + { + var handlerWithInfo = contactEventHandler.Value as IContactEventHandlerWithInfo; + + if (handlerWithInfo != null) + { + m_HandlerInfo[contactEventHandler.Key] = handlerWithInfo.GetContactEventHandlerInfo(); + } + else + { + var info = m_HandlerInfo[contactEventHandler.Key]; + info.HasContactEventPriority = !m_RigidbodyMapping[contactEventHandler.Key].isKinematic; + m_HandlerInfo[contactEventHandler.Key] = info; + } + } + + ContactEventHandlerInfo contactEventHandlerInfo0; + ContactEventHandlerInfo contactEventHandlerInfo1; + // Process all collisions for (int i = 0; i < m_Count; i++) { var thisInstanceID = m_ResultsArray[i].ThisInstanceID; var otherInstanceID = m_ResultsArray[i].OtherInstanceID; - var rb0Valid = thisInstanceID != 0 && m_RigidbodyMapping.ContainsKey(thisInstanceID); - var rb1Valid = otherInstanceID != 0 && m_RigidbodyMapping.ContainsKey(otherInstanceID); - // Only notify registered rigid bodies. - if (!rb0Valid || !rb1Valid || !m_HandlerMapping.ContainsKey(thisInstanceID)) + var contactHandler0 = (IContactEventHandler)null; + var contactHandler1 = (IContactEventHandler)null; + var preferredContactHandler = (IContactEventHandler)null; + var preferredContactHandlerNonRigidbody = false; + var preferredRigidbody = (Rigidbody)null; + var otherContactHandler = (IContactEventHandler)null; + var otherRigidbody = (Rigidbody)null; + + var otherContactHandlerNonRigidbody = false; + + if (m_RigidbodyMapping.ContainsKey(thisInstanceID)) + { + contactHandler0 = m_HandlerMapping[thisInstanceID]; + contactEventHandlerInfo0 = m_HandlerInfo[thisInstanceID]; + if (contactEventHandlerInfo0.HasContactEventPriority) + { + preferredContactHandler = contactHandler0; + preferredContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents; + preferredRigidbody = m_RigidbodyMapping[thisInstanceID]; + } + else + { + otherContactHandler = contactHandler0; + otherContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents; + otherRigidbody = m_RigidbodyMapping[thisInstanceID]; + } + } + + if (m_RigidbodyMapping.ContainsKey(otherInstanceID)) + { + contactHandler1 = m_HandlerMapping[otherInstanceID]; + contactEventHandlerInfo1 = m_HandlerInfo[otherInstanceID]; + if (contactEventHandlerInfo1.HasContactEventPriority && preferredContactHandler == null) + { + preferredContactHandler = contactHandler1; + preferredContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents; + preferredRigidbody = m_RigidbodyMapping[otherInstanceID]; + } + else + { + otherContactHandler = contactHandler1; + otherContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents; + otherRigidbody = m_RigidbodyMapping[otherInstanceID]; + } + } + + if (preferredContactHandler == null && otherContactHandler != null) + { + preferredContactHandler = otherContactHandler; + preferredContactHandlerNonRigidbody = otherContactHandlerNonRigidbody; + preferredRigidbody = otherRigidbody; + otherContactHandler = null; + otherContactHandlerNonRigidbody = false; + otherRigidbody = null; + } + + if (preferredContactHandler == null || (preferredContactHandler != null && otherContactHandler == null && !preferredContactHandlerNonRigidbody)) { continue; } + if (m_ResultsArray[i].HasCollisionStay) { - m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal); + preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal); } else { - m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint); + preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index 9ec32bc57f..601a1baccb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -1,5 +1,7 @@ #if COM_UNITY_MODULES_PHYSICS using System.Collections; +using System.Collections.Generic; +using System.Text; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; @@ -108,5 +110,386 @@ public IEnumerator TestRigidbodyKinematicEnableDisable() Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!"); } } + + internal class ContactEventTransformHelperWithInfo : ContactEventTransformHelper, IContactEventHandlerWithInfo + { + public ContactEventHandlerInfo GetContactEventHandlerInfo() + { + var contactEventHandlerInfo = new ContactEventHandlerInfo() + { + HasContactEventPriority = IsOwner, + ProvideNonRigidBodyContactEvents = m_EnableNonRigidbodyContacts.Value, + }; + return contactEventHandlerInfo; + } + + protected override void OnRegisterForContactEvents(bool isRegistering) + { + RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); + } + } + + + internal class ContactEventTransformHelper : NetworkTransform, IContactEventHandler + { + public static Vector3 SessionOwnerSpawnPoint; + public static Vector3 ClientSpawnPoint; + public static bool VerboseDebug; + public enum HelperStates + { + None, + MoveForward, + } + + private HelperStates m_HelperState; + + public void SetHelperState(HelperStates state) + { + m_HelperState = state; + if (!m_NetworkRigidbody.IsKinematic()) + { + m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; + m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; + } + m_NetworkRigidbody.Rigidbody.isKinematic = m_HelperState == HelperStates.None; + if (!m_NetworkRigidbody.IsKinematic()) + { + m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; + m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; + } + + } + + protected struct ContactEventInfo + { + public ulong EventId; + public Vector3 AveragedCollisionNormal; + public Rigidbody CollidingBody; + public Vector3 ContactPoint; + } + + protected List m_ContactEvents = new List(); + + protected NetworkVariable m_EnableNonRigidbodyContacts = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + protected NetworkRigidbody m_NetworkRigidbody; + public ContactEventTransformHelper Target; + + public bool HasContactEvents() + { + return m_ContactEvents.Count > 0; + } + + public Rigidbody GetRigidbody() + { + return m_NetworkRigidbody.Rigidbody; + } + + public bool HadContactWith(ContactEventTransformHelper otherObject) + { + if (otherObject == null) + { + return false; + } + foreach (var contactEvent in m_ContactEvents) + { + if (contactEvent.CollidingBody == otherObject.m_NetworkRigidbody.Rigidbody) + { + return true; + } + } + return false; + } + + protected virtual void CheckToStopMoving() + { + SetHelperState(HadContactWith(Target) ? HelperStates.None : HelperStates.MoveForward); + } + + public void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default) + { + if (Target == null) + { + return; + } + + if (collidingBody != null) + { + Log($">>>>>>> contact event with {collidingBody.name}!"); + } + else + { + Log($">>>>>>> contact event with non-rigidbody!"); + } + + m_ContactEvents.Add(new ContactEventInfo() + { + EventId = eventId, + AveragedCollisionNormal = averagedCollisionNormal, + CollidingBody = collidingBody, + ContactPoint = contactPoint, + }); + CheckToStopMoving(); + } + + private void SetInitialPositionClientServer() + { + if (IsServer) + { + if (!NetworkManager.DistributedAuthorityMode && !IsLocalPlayer) + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + else + { + transform.position = SessionOwnerSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + } + } + else + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + } + + private void SetInitialPositionDistributedAuthority() + { + if (HasAuthority) + { + if (IsSessionOwner) + { + transform.position = SessionOwnerSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + } + else + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + } + } + + public override void OnNetworkSpawn() + { + m_NetworkRigidbody = GetComponent(); + + m_NetworkRigidbody.Rigidbody.maxLinearVelocity = 15; + m_NetworkRigidbody.Rigidbody.maxAngularVelocity = 10; + + if (NetworkManager.DistributedAuthorityMode) + { + SetInitialPositionDistributedAuthority(); + } + else + { + SetInitialPositionClientServer(); + } + if (IsLocalPlayer) + { + RegisterForContactEvents(true); + } + else + { + m_NetworkRigidbody.Rigidbody.detectCollisions = false; + } + base.OnNetworkSpawn(); + } + + protected virtual void OnRegisterForContactEvents(bool isRegistering) + { + RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); + } + + public void RegisterForContactEvents(bool isRegistering) + { + OnRegisterForContactEvents(isRegistering); + } + + private void FixedUpdate() + { + if (!IsSpawned || !IsOwner || m_HelperState != HelperStates.MoveForward) + { + return; + } + var distance = Vector3.Distance(Target.transform.position, transform.position); + var moveAmount = Mathf.Max(1.2f, distance); + // Head towards our target + var dir = (Target.transform.position - transform.position).normalized; + var deltaMove = dir * moveAmount * Time.fixedDeltaTime; + m_NetworkRigidbody.Rigidbody.MovePosition(m_NetworkRigidbody.Rigidbody.position + deltaMove); + + + Log($" Loc: {transform.position} | Dest: {Target.transform.position} | Dist: {distance} | MoveDelta: {deltaMove}"); + } + + protected void Log(string msg) + { + if (VerboseDebug) + { + Debug.Log($"Client-{OwnerClientId} {msg}"); + } + } + } + + [TestFixture(HostOrServer.Host, ContactEventTypes.Default)] + [TestFixture(HostOrServer.DAHost, ContactEventTypes.Default)] + [TestFixture(HostOrServer.Host, ContactEventTypes.WithInfo)] + [TestFixture(HostOrServer.DAHost, ContactEventTypes.WithInfo)] + internal class RigidbodyContactEventManagerTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 1; + + + private GameObject m_RigidbodyContactEventManager; + + public enum ContactEventTypes + { + Default, + WithInfo + } + + private ContactEventTypes m_ContactEventType; + private StringBuilder m_ErrorLogger = new StringBuilder(); + + public RigidbodyContactEventManagerTests(HostOrServer hostOrServer, ContactEventTypes contactEventType) : base(hostOrServer) + { + m_ContactEventType = contactEventType; + } + + protected override void OnCreatePlayerPrefab() + { + ContactEventTransformHelper.SessionOwnerSpawnPoint = GetRandomVector3(-4, -3); + ContactEventTransformHelper.ClientSpawnPoint = GetRandomVector3(3, 4); + if (m_ContactEventType == ContactEventTypes.Default) + { + var helper = m_PlayerPrefab.AddComponent(); + helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + } + else + { + var helperWithInfo = m_PlayerPrefab.AddComponent(); + helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + } + + var rigidbody = m_PlayerPrefab.AddComponent(); + rigidbody.useGravity = false; + rigidbody.isKinematic = true; + rigidbody.mass = 5.0f; + rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous; + var sphereCollider = m_PlayerPrefab.AddComponent(); + sphereCollider.radius = 0.5f; + sphereCollider.providesContacts = true; + + var networkRigidbody = m_PlayerPrefab.AddComponent(); + networkRigidbody.UseRigidBodyForMotion = true; + networkRigidbody.AutoUpdateKinematicState = false; + + m_RigidbodyContactEventManager = new GameObject(); + m_RigidbodyContactEventManager.AddComponent(); + } + + + + private bool PlayersSpawnedInRightLocation() + { + var position = m_ServerNetworkManager.LocalClient.PlayerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); + return false; + } + + position = m_ClientNetworkManagers[0].LocalClient.PlayerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); + return false; + } + var playerObject = (NetworkObject)null; + if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClient.PlayerObject.NetworkObjectId)) + { + m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} cannot find a local spawned instance of Client-{m_ClientNetworkManagers[0].LocalClientId}'s player object!"); + return false; + } + playerObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_ClientNetworkManagers[0].LocalClient.PlayerObject.NetworkObjectId]; + position = playerObject.transform.position; + + if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); + return false; + } + + if (!m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId)) + { + m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} cannot find a local spawned instance of Client-{m_ServerNetworkManager.LocalClientId}'s player object!"); + return false; + } + playerObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId]; + position = playerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, playerObject.transform.position)) + { + m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); + return false; + } + return true; + } + + + [UnityTest] + public IEnumerator TestContactEvents() + { + ContactEventTransformHelper.VerboseDebug = m_EnableVerboseDebug; + + m_PlayerPrefab.SetActive(false); + m_ErrorLogger.Clear(); + // Validate all instances are spawned in the right location + yield return WaitForConditionOrTimeOut(PlayersSpawnedInRightLocation); + AssertOnTimeout($"Timed out waiting for all player instances to spawn in the corect location:\n {m_ErrorLogger}"); + m_ErrorLogger.Clear(); + + var sessionOwnerPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent() : + m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent(); + var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent() : + m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); + + // Get both players to point towards each other + sessionOwnerPlayer.Target = clientPlayer; + clientPlayer.Target = sessionOwnerPlayer; + + sessionOwnerPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); + clientPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); + + + yield return WaitForConditionOrTimeOut(() => sessionOwnerPlayer.HadContactWith(clientPlayer) || clientPlayer.HadContactWith(sessionOwnerPlayer)); + AssertOnTimeout("Timed out waiting for a player to collide with another player!"); + + clientPlayer.RegisterForContactEvents(false); + sessionOwnerPlayer.RegisterForContactEvents(false); + var otherPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ServerNetworkManager.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent() : + m_ServerNetworkManager.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent(); + otherPlayer.RegisterForContactEvents(false); + otherPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent() : + m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent(); + otherPlayer.RegisterForContactEvents(false); + + Object.Destroy(m_RigidbodyContactEventManager); + m_RigidbodyContactEventManager = null; + } + + protected override IEnumerator OnTearDown() + { + // In case of a test failure + if (m_RigidbodyContactEventManager) + { + Object.Destroy(m_RigidbodyContactEventManager); + m_RigidbodyContactEventManager = null; + } + + return base.OnTearDown(); + } + } } #endif // COM_UNITY_MODULES_PHYSICS From e9b7ef4793c2afc8c02c150f41669c977e412cfb Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 11 Oct 2024 13:44:34 -0500 Subject: [PATCH 108/236] fix: scene loading event not synchronizing in scene objects (#3096) * fix This fixes the issue with in-scene placed NetworkObjects not being synchronized properly when the session owner generates a scene event type load. The service is not considered an observer and when sending a scene event type load it should send all spawned objects. * update adding changelog entry * update adding associated PR to changelog entry * update covering synchronization too. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/SceneManagement/SceneEventData.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 32fea4c828..dc89f55dfb 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -18,6 +18,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 18ac6a7ed1..613c216328 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -320,9 +320,11 @@ private void SortParentedNetworkObjects() internal void AddSpawnedNetworkObjects() { m_NetworkObjectsSync.Clear(); + // If distributed authority mode and sending to the service, then ignore observers + var distributedAuthoritySendingToService = m_NetworkManager.DistributedAuthorityMode && TargetClientId == NetworkManager.ServerClientId; foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList) { - if (sobj.Observers.Contains(TargetClientId)) + if (sobj.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService) { m_NetworkObjectsSync.Add(sobj); } @@ -666,12 +668,14 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) // Write our count place holder (must not be packed!) writer.WriteValueSafe((ushort)0); var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; + // If distributed authority mode and sending to the service, then ignore observers + var distributedAuthoritySendingToService = distributedAuthority && TargetClientId == NetworkManager.ServerClientId; foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects) { foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value) { - if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId)) + if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService) { // Serialize the NetworkObject var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority); From 5f7441c3f955e9b2b58840e4920ca1019649aa72 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 14 Oct 2024 18:01:29 -0500 Subject: [PATCH 109/236] chore: adjust ConnectionRequestMessage to use ClientConfig version as opposed to NGO version (#3098) * update This removes the NGO version number from the client configuration in favor of an incremented version number. * update removes the NetworkManager NGO version inspector view code. --- .../Editor/NetworkManagerEditor.cs | 23 ---------- .../Connection/NetworkConnectionManager.cs | 1 - .../Runtime/Core/NetworkManager.cs | 44 ------------------- .../Messages/ConnectionRequestMessage.cs | 34 +++++++------- 4 files changed, 16 insertions(+), 86 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index b93e7d90d5..405291749f 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -48,12 +48,6 @@ public class NetworkManagerEditor : NetcodeEditorBase private SerializedProperty m_NetworkProfileMetrics; private SerializedProperty m_NetworkMessageMetrics; -#if CMB_SERVICE_DEVELOPMENT - private SerializedProperty m_MajorVersion; - private SerializedProperty m_MinorVersion; - private SerializedProperty m_PatchVersion; -#endif - private NetworkManager m_NetworkManager; private bool m_Initialized; @@ -125,11 +119,6 @@ private void Initialize() m_NetworkProfileMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkProfileMetrics"); #if MULTIPLAYER_TOOLS m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics"); -#endif -#if CMB_SERVICE_DEVELOPMENT - m_MajorVersion = serializedObject.FindProperty(nameof(NetworkManager.MajorVersion)); - m_MinorVersion = serializedObject.FindProperty(nameof(NetworkManager.MinorVersion)); - m_PatchVersion = serializedObject.FindProperty(nameof(NetworkManager.PatchVersion)); #endif m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty @@ -170,11 +159,6 @@ private void CheckNullProperties() #if MULTIPLAYER_TOOLS m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics"); #endif -#if CMB_SERVICE_DEVELOPMENT - m_MajorVersion = serializedObject.FindProperty(nameof(NetworkManager.MajorVersion)); - m_MinorVersion = serializedObject.FindProperty(nameof(NetworkManager.MinorVersion)); - m_PatchVersion = serializedObject.FindProperty(nameof(NetworkManager.PatchVersion)); -#endif m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_PrefabsList = m_NetworkConfigProperty @@ -192,13 +176,6 @@ private void DisplayNetworkManagerProperties() EditorGUILayout.PropertyField(m_LogLevelProperty); EditorGUILayout.Space(); -#if CMB_SERVICE_DEVELOPMENT - EditorGUILayout.LabelField("Version:", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_MajorVersion); - EditorGUILayout.PropertyField(m_MinorVersion); - EditorGUILayout.PropertyField(m_PatchVersion); - EditorGUILayout.Space(); -#endif EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel); #if MULTIPLAYER_SERVICES_SDK_INSTALLED EditorGUILayout.PropertyField(m_NetworkTopologyProperty); diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 33098f36c2..5c26df4b91 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -573,7 +573,6 @@ private void SendConnectionRequest() if (NetworkManager.CMBServiceConnection) { - message.ClientConfig.NGOVersion = NetworkManager.GetNGOVersion(); message.ClientConfig.TickRate = NetworkManager.NetworkConfig.TickRate; message.ClientConfig.EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 0bb73b3a37..1cac5625e8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -887,29 +887,6 @@ internal T Value internal Override PortOverride; - [HideInInspector] - [SerializeField] - [Range(0, 255)] - internal byte MajorVersion; - [HideInInspector] - [SerializeField] - [Range(0, 255)] - internal byte MinorVersion; - [HideInInspector] - [SerializeField] - [Range(0, 255)] - internal byte PatchVersion; - - internal NGOVersion GetNGOVersion() - { - return new NGOVersion() - { - Major = MajorVersion, - Minor = MinorVersion, - Patch = PatchVersion - }; - } - #if UNITY_EDITOR internal static INetworkManagerHelper NetworkManagerHelper; @@ -941,21 +918,6 @@ private PackageInfo GetPackageInfo(string packageName) return AssetDatabase.FindAssets("package").Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath(x) != null).Select(PackageInfo.FindForAssetPath).Where(x => x != null).First(x => x.name == packageName); } - private void SetPackageVersion() - { - var packageInfo = GetPackageInfo("com.unity.netcode.gameobjects"); - if (packageInfo != null) - { - var versionSplit = packageInfo.version.Split("."); - if (versionSplit.Length == 3) - { - MajorVersion = byte.Parse(versionSplit[0]); - MinorVersion = byte.Parse(versionSplit[1]); - PatchVersion = byte.Parse(versionSplit[2]); - } - } - } - internal void OnValidate() { if (NetworkConfig == null) @@ -963,12 +925,6 @@ internal void OnValidate() return; // May occur when the component is added } -#if !CMB_SERVICE_DEVELOPMENT - SetPackageVersion(); -#else - Debug.Log($"Major:({MajorVersion}) Minor({MinorVersion}) Patch({PatchVersion})"); -#endif - if (GetComponentInChildren() != null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index 87d49cb332..d8e60d2537 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -2,39 +2,37 @@ namespace Unity.Netcode { - internal struct NGOVersion : INetworkSerializable - { - public byte Major; - public byte Minor; - public byte Patch; - - public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter - { - serializer.SerializeValue(ref Major); - serializer.SerializeValue(ref Minor); - serializer.SerializeValue(ref Patch); - } - } - + /// + /// Only used when connecting to the distributed authority service + /// internal struct ClientConfig : INetworkSerializable { - public NGOVersion NGOVersion; + /// + /// We start at version 1, where anything less than version 1 on the service side + /// is not bypass feature compatible. + /// + private const int k_BypassFeatureCompatible = 1; + public int Version => k_BypassFeatureCompatible; public uint TickRate; public bool EnableSceneManagement; + // Only gets deserialized but should never be used unless testing + public int RemoteClientVersion; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { - serializer.SerializeNetworkSerializable(ref NGOVersion); if (serializer.IsWriter) { var writer = serializer.GetFastBufferWriter(); - writer.WriteValueSafe(TickRate); + BytePacker.WriteValueBitPacked(writer, Version); + BytePacker.WriteValueBitPacked(writer, TickRate); writer.WriteValueSafe(EnableSceneManagement); } else { var reader = serializer.GetFastBufferReader(); - reader.ReadValueSafe(out TickRate); + ByteUnpacker.ReadValueBitPacked(reader, out RemoteClientVersion); + ByteUnpacker.ReadValueBitPacked(reader, out TickRate); reader.ReadValueSafe(out EnableSceneManagement); } } From 36252471f9e2a61a5bcc169aa46c26505a72e969 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 14 Oct 2024 18:49:50 -0500 Subject: [PATCH 110/236] fix: client-server owner authoritative nested NetworkTransform invalid synchronization (#3099) * fix This fixes the issue when using a client-server network topology spawning a player with nested NetworkTransform components would result in loss of the child NetworkTransform component(s) settings/flags which would end up being serialized to the owner client (thus causing invalid synchronization results). * update Adding the changelog entry. * fix The issue is not just for local players but for all prefabs that have nested NetworkTransforms, are owner authoritative, are not owned by the server, and the session instance is using a client-server network topology. * test Adding a test to validate this fix. * update adding PR number to changelog entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Components/NetworkTransform.cs | 33 ++- .../NetworkTransformOwnershipTests.cs | 275 ++++++++++++++++++ 3 files changed, 308 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index dc89f55dfb..9567bc8844 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -18,6 +18,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue with nested `NetworkTransform` components clearing their initial prefab settings when in owner authoritative mode on the server side while using a client-server network topology which resulted in improper synchronization of the nested `NetworkTransform` components. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d4d1f93a98..a103b0fc4b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1487,7 +1487,6 @@ protected override void OnSynchronize(ref BufferSerializer serializer) HalfVectorRotation = new HalfVector4(), HalfVectorScale = new HalfVector3(), NetworkDeltaPosition = new NetworkDeltaPosition(), - }; if (serializer.IsWriter) @@ -3050,12 +3049,44 @@ protected internal override void InternalOnNetworkSessionSynchronized() base.InternalOnNetworkSessionSynchronized(); } + private void ApplyPlayerTransformState() + { + SynchronizeState.InLocalSpace = InLocalSpace; + SynchronizeState.UseInterpolation = Interpolate; + SynchronizeState.QuaternionSync = UseQuaternionSynchronization; + SynchronizeState.UseHalfFloatPrecision = UseHalfFloatPrecision; + SynchronizeState.QuaternionCompression = UseQuaternionCompression; + SynchronizeState.UsePositionSlerp = SlerpPosition; + } + /// /// For dynamically spawned NetworkObjects, when the non-authority instance's client is already connected and /// the SynchronizeState is still pending synchronization then we want to finalize the synchornization at this time. /// protected internal override void InternalOnNetworkPostSpawn() { + // This is a special case for client-server where a server is spawning an owner authoritative NetworkObject but has yet to serialize anything. + // When the server detects that: + // - We are not in a distributed authority session (DAHost check). + // - This is the first/root NetworkTransform. + // - We are in owner authoritative mode. + // - The NetworkObject is not owned by the server. + // - The SynchronizeState.IsSynchronizing is set to false. + // Then we want to: + // - Force the "IsSynchronizing" flag so the NetworkTransform has its state updated properly and runs through the initialization again. + // - Make sure the SynchronizingState is updated to the instantiated prefab's default flags/settings. + if (NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode && m_IsFirstNetworkTransform && !OnIsServerAuthoritative() && !IsOwner && !SynchronizeState.IsSynchronizing) + { + // Assure the first/root NetworkTransform has the synchronizing flag set so the server runs through the final post initialization steps + SynchronizeState.IsSynchronizing = true; + // Assure the SynchronizeState matches the initial prefab's values for each associated NetworkTransfrom (this includes root + all children) + foreach (var child in NetworkObject.NetworkTransforms) + { + child.ApplyPlayerTransformState(); + } + // Now fall through to the final synchronization portion of the spawning for NetworkTransform + } + if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) { NonAuthorityFinalizeSynchronization(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 8f65f34694..31105efadd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; @@ -539,5 +540,279 @@ protected override bool OnIsServerAuthoritative() } } } + + [TestFixture(HostOrServer.DAHost, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using distributed authority + [TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Server)] // Validate we have not impacted NetworkTransform server authoritative mode + [TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using client-server + internal class NestedNetworkTransformTests : IntegrationTestWithApproximation + { + private const int k_NestedChildren = 5; + protected override int NumberOfClients => 2; + + private GameObject m_SpawnObject; + + private NetworkTransform.AuthorityModes m_AuthorityMode; + + private StringBuilder m_ErrorLog = new StringBuilder(); + + private List m_NetworkManagers = new List(); + private List m_SpawnedObjects = new List(); + + public NestedNetworkTransformTests(HostOrServer hostOrServer, NetworkTransform.AuthorityModes authorityMode) : base(hostOrServer) + { + m_AuthorityMode = authorityMode; + } + + /// + /// Creates a player prefab with several nested NetworkTransforms + /// + protected override void OnCreatePlayerPrefab() + { + var networkTransform = m_PlayerPrefab.AddComponent(); + networkTransform.AuthorityMode = m_AuthorityMode; + var parent = m_PlayerPrefab; + // Add several nested NetworkTransforms + for (int i = 0; i < k_NestedChildren; i++) + { + var nestedChild = new GameObject(); + nestedChild.transform.parent = parent.transform; + var nestedNetworkTransform = nestedChild.AddComponent(); + nestedNetworkTransform.AuthorityMode = m_AuthorityMode; + nestedNetworkTransform.InLocalSpace = true; + parent = nestedChild; + } + base.OnCreatePlayerPrefab(); + } + + private void RandomizeObjectTransformPositions(GameObject gameObject) + { + var networkObject = gameObject.GetComponent(); + Assert.True(networkObject.ChildNetworkBehaviours.Count > 0); + + foreach (var networkTransform in networkObject.NetworkTransforms) + { + networkTransform.gameObject.transform.position = GetRandomVector3(-15.0f, 15.0f); + } + } + + /// + /// Randomizes each player's position when validating distributed authority + /// + /// + private GameObject FetchLocalPlayerPrefabToSpawn() + { + RandomizeObjectTransformPositions(m_PlayerPrefab); + return m_PlayerPrefab; + } + + /// + /// Randomizes the player position when validating client-server + /// + /// + /// + private void ConnectionApprovalHandler(NetworkManager.ConnectionApprovalRequest connectionApprovalRequest, NetworkManager.ConnectionApprovalResponse connectionApprovalResponse) + { + connectionApprovalResponse.Approved = true; + connectionApprovalResponse.CreatePlayerObject = true; + RandomizeObjectTransformPositions(m_PlayerPrefab); + connectionApprovalResponse.Position = GetRandomVector3(-15.0f, 15.0f); + } + + protected override void OnServerAndClientsCreated() + { + // Create a prefab to spawn with each NetworkManager as the owner + m_SpawnObject = CreateNetworkObjectPrefab("SpawnObj"); + var networkTransform = m_SpawnObject.AddComponent(); + networkTransform.AuthorityMode = m_AuthorityMode; + var parent = m_SpawnObject; + // Add several nested NetworkTransforms + for (int i = 0; i < k_NestedChildren; i++) + { + var nestedChild = new GameObject(); + nestedChild.transform.parent = parent.transform; + var nestedNetworkTransform = nestedChild.AddComponent(); + nestedNetworkTransform.AuthorityMode = m_AuthorityMode; + nestedNetworkTransform.InLocalSpace = true; + parent = nestedChild; + } + + if (m_DistributedAuthority) + { + if (!UseCMBService()) + { + m_ServerNetworkManager.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn; + } + + foreach (var client in m_ClientNetworkManagers) + { + client.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn; + } + } + else + { + m_ServerNetworkManager.NetworkConfig.ConnectionApproval = true; + m_ServerNetworkManager.ConnectionApprovalCallback += ConnectionApprovalHandler; + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.ConnectionApproval = true; + } + } + + base.OnServerAndClientsCreated(); + } + + /// + /// Validates the transform positions of two NetworkObject instances + /// + /// the local instance (source of truth) + /// the remote instance + /// + private bool ValidateTransforms(NetworkObject current, NetworkObject testing) + { + if (current.ChildNetworkBehaviours.Count == 0 || testing.ChildNetworkBehaviours.Count == 0) + { + return false; + } + + for (int i = 0; i < current.NetworkTransforms.Count - 1; i++) + { + var transformA = current.NetworkTransforms[i].transform; + var transformB = testing.NetworkTransforms[i].transform; + if (!Approximately(transformA.position, transformB.position)) + { + m_ErrorLog.AppendLine($"TransformA Position {transformA.position} != TransformB Position {transformB.position}"); + return false; + } + if (!Approximately(transformA.localPosition, transformB.localPosition)) + { + m_ErrorLog.AppendLine($"TransformA Local Position {transformA.position} != TransformB Local Position {transformB.position}"); + return false; + } + if (transformA.parent != null) + { + if (current.NetworkTransforms[i].InLocalSpace != testing.NetworkTransforms[i].InLocalSpace) + { + m_ErrorLog.AppendLine($"NetworkTransform-{current.OwnerClientId}-{current.NetworkTransforms[i].NetworkBehaviourId} InLocalSpace ({current.NetworkTransforms[i].InLocalSpace}) is different from the remote instance version on Client-{testing.NetworkManager.LocalClientId}!"); + return false; + } + } + } + return true; + } + + /// + /// Validates all player instances spawned with the correct positions including all nested NetworkTransforms + /// When running in server authority mode we are validating this fix did not impact that. + /// + private bool AllClientInstancesSynchronized() + { + m_ErrorLog.Clear(); + + foreach (var current in m_NetworkManagers) + { + var currentPlayer = current.LocalClient.PlayerObject; + var currentNetworkObjectId = currentPlayer.NetworkObjectId; + foreach (var testing in m_NetworkManagers) + { + if (currentPlayer == testing.LocalClient.PlayerObject) + { + continue; + } + + if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId)) + { + m_ErrorLog.AppendLine($"Failed to find Client-{currentPlayer.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!"); + return false; + } + + var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId]; + if (!ValidateTransforms(currentPlayer, remoteInstance)) + { + m_ErrorLog.AppendLine($"Failed to validate Client-{currentPlayer.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!"); + return false; + } + } + } + return true; + } + + /// + /// Validates that dynamically spawning works the same. + /// When running in server authority mode we are validating this fix did not impact that. + /// + /// + private bool AllSpawnedObjectsSynchronized() + { + m_ErrorLog.Clear(); + + foreach (var current in m_SpawnedObjects) + { + var currentNetworkObject = current.GetComponent(); + var currentNetworkObjectId = currentNetworkObject.NetworkObjectId; + foreach (var testing in m_NetworkManagers) + { + if (currentNetworkObject.OwnerClientId == testing.LocalClientId) + { + continue; + } + + if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId)) + { + m_ErrorLog.AppendLine($"Failed to find Client-{currentNetworkObject.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!"); + return false; + } + + var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId]; + if (!ValidateTransforms(currentNetworkObject, remoteInstance)) + { + m_ErrorLog.AppendLine($"Failed to validate Client-{currentNetworkObject.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!"); + return false; + } + } + } + return true; + } + + /// + /// Validates that spawning player and dynamically spawned prefab instances with nested NetworkTransforms + /// synchronizes properly in both client-server and distributed authority when using owner authoritative mode. + /// + [UnityTest] + public IEnumerator NestedNetworkTransformSpawnPositionTest() + { + if (!m_DistributedAuthority || (m_DistributedAuthority && !UseCMBService())) + { + m_NetworkManagers.Add(m_ServerNetworkManager); + } + m_NetworkManagers.AddRange(m_ClientNetworkManagers); + + yield return WaitForConditionOrTimeOut(AllClientInstancesSynchronized); + AssertOnTimeout($"Failed to synchronize all client instances!\n{m_ErrorLog}"); + + foreach (var networkManager in m_NetworkManagers) + { + // Randomize the position + RandomizeObjectTransformPositions(m_SpawnObject); + + // Create an instance owned by the specified networkmanager + m_SpawnedObjects.Add(SpawnObject(m_SpawnObject, networkManager)); + } + // Randomize the position once more just to assure we are instantiating remote instances + // with a completely different position + RandomizeObjectTransformPositions(m_SpawnObject); + yield return WaitForConditionOrTimeOut(AllSpawnedObjectsSynchronized); + AssertOnTimeout($"Failed to synchronize all spawned NetworkObject instances!\n{m_ErrorLog}"); + m_SpawnedObjects.Clear(); + m_NetworkManagers.Clear(); + } + + protected override IEnumerator OnTearDown() + { + // In case there was a failure, go ahead and clear these lists out for any pending TextFixture passes + m_SpawnedObjects.Clear(); + m_NetworkManagers.Clear(); + return base.OnTearDown(); + } + } } #endif From 6b459a1fa9c6db24d6acfbc66e47d1ea5ce2b6cc Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 14 Oct 2024 20:09:07 -0500 Subject: [PATCH 111/236] fix: minor fixes for 2.1.0 (#3097) * style Spelling fix for error message * update Expose auto spawn player prefab within the inspector view when multiplayer SDK is installed. Remove the automatic enabling of this property if distributed authority is set. * update moving property down * update adding changelog entries * update Only show the AutoSpawnPlayerPrefabClientSide property in the inspector view if the NetworkManager instance is configured to use the distributed authority network topology. --- com.unity.netcode.gameobjects/CHANGELOG.md | 5 ++++- .../Editor/NetworkManagerEditor.cs | 20 +++++++++++++++++++ .../Runtime/Core/NetworkManager.cs | 3 --- .../Runtime/Core/NetworkObject.cs | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9567bc8844..f56da05c9c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -9,7 +9,8 @@ Additional documentation and release notes are available at [Multiplayer Documen [Unreleased] ### Added - + +- Added ability to edit the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` within the inspector view. (#3097) - Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094) - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094) - `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094) @@ -27,6 +28,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097) + ## [2.0.0] - 2024-09-12 ### Added diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 405291749f..0684d74849 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -31,6 +31,7 @@ public class NetworkManagerEditor : NetcodeEditorBase private SerializedProperty m_NetworkTransportProperty; private SerializedProperty m_TickRateProperty; #if MULTIPLAYER_SERVICES_SDK_INSTALLED + private SerializedProperty m_AutoSpawnPlayerPrefabClientSide; private SerializedProperty m_NetworkTopologyProperty; #endif private SerializedProperty m_ClientConnectionBufferTimeoutProperty; @@ -104,6 +105,11 @@ private void Initialize() m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); #if MULTIPLAYER_SERVICES_SDK_INSTALLED m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology"); + // Only display the auto spawn property when the distributed authority network topology is selected + if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) + { + m_AutoSpawnPlayerPrefabClientSide = m_NetworkConfigProperty.FindPropertyRelative("AutoSpawnPlayerPrefabClientSide"); + } #endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); @@ -142,6 +148,11 @@ private void CheckNullProperties() m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); #if MULTIPLAYER_SERVICES_SDK_INSTALLED m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology"); + // Only display the auto spawn property when the distributed authority network topology is selected + if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) + { + m_AutoSpawnPlayerPrefabClientSide = m_NetworkConfigProperty.FindPropertyRelative("AutoSpawnPlayerPrefabClientSide"); + } #endif m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); @@ -221,8 +232,17 @@ private void DisplayNetworkManagerProperties() EditorGUILayout.Space(); EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty); +#if MULTIPLAYER_SERVICES_SDK_INSTALLED + // Only display the auto spawn property when the distributed authority network topology is selected + if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) + { + EditorGUILayout.PropertyField(m_AutoSpawnPlayerPrefabClientSide, new GUIContent("Auto Spawn Player Prefab")); + } +#endif EditorGUILayout.PropertyField(m_PlayerPrefabProperty, new GUIContent("Default Player Prefab")); + + if (m_NetworkManager.NetworkConfig.HasOldPrefabList()) { EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 1cac5625e8..de08ae0ebf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1161,9 +1161,6 @@ internal void Initialize(bool server) UpdateTopology(); - //DANGOEXP TODO: Remove this before finalizing the experimental release - NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode; - // Make sure the ServerShutdownState is reset when initializing if (server) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 9cd176c39b..d1582c6f03 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -336,7 +336,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true) if (!HasAuthority) { - NetworkLog.LogError($"Only the authoirty can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!"); + NetworkLog.LogError($"Only the authority can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!"); return; } From 632bf3527b2f36615970791dabf6f7f69049651d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 16 Oct 2024 15:24:33 -0500 Subject: [PATCH 112/236] fix: networkvariable collections can be modified without write permissions and more (#3081) * fix Provide additional copy of last known current NetworkVariable.Value to be able to compare against the current local value in order to detect if a client without write permissions has modified a collection. * test Updated collections validation tests to spot check the restore known current state when client without write permissions modifies a collection. * update added changelog entry * fix Changing order of operations within CheckDirtyState for clients without write permissions * test Add some spot checks to one of the dictionary tests * fix Fixing issue where upon a client gaining ownership of a NetworkObject that has one or more owner write permission NetworkVariables using a collection type with keyed indices (like a dictionary) can resend already synchronized entries when making a change to the collection causing non-owner clients to throw a key already exists exception. * update Adding changelog entrry * update Adjusted when the NetworkVariable update for ownership change is invoked to account for updates to owner write NetworkVariables within the OnGainedOwnership callback. When changing ownership: - Marking any owner read permissions NetworkVariables as dirty when sending to the new owner (both within NetworkSpawnManager for server-side update and within the NetworkVariableDeltaMessage). - Sending any pending updates to all clients prior to sending the change in ownership message to assure pending updates are synchronized as the owner. When initially synchronizing a client, if a NetworkVariable has a pending state update then send serialize the previously known value(s) to the synchronizing client so when the pending updates are sent they don't duplicate values. * test Adjusting two deferred message tests to not account for a NetworkVariable delta state update message when changing ownership and there are no pending updates. * update updating changelog entries * style * fix This includes additional fixes for NetworkVariable collections that ended up requiring a different approach to how a server forwards NetworkVariable deltas. Now, NetworkVariableDeltaMessage forwards NetworkVariable field deltas immediately after a server has finished processing the deltas (i.e. the keeping a NetworkVariable dirty concept is not used from this point forward). I went ahead and kept the compatibility of this functionality. NetworkVariableDeltaMessage has had its Version incremented as we now send the NetworkDelivery type in order to be able to handle this (it seemed less risky to include this than to try and bubble up this property to all message types). This also separates the duplication of the "original internal value" and the "previous value" until after processing all deltas. If the server has to foward, it forwards first then invokes the PostDeltaRead method that performs the duplication. This includes some minor adjustments to NetworkList in order to adjust to this new order of operations while also preserving the legacy approach. This also includes some adjustments to NetworkBehaviourUpdater where it can force a send (flush) any pending dirty fields when changing ownership. This includes some minor modifications to NetworkObject.PostNetworkVariableWrite. * test Just some updates to two integration tests. These are all primarily for debugging purposes. * update Adding last change log entry for this PR. * style Updated a changelog entry to make it clearer. * fix DAHost fixes: Fixed issue where it was possible to ignore forwarding a change in ownership message to the destination owner if the original owner changed ownership. Fixed issue where it was possible to re-send a change in ownership message back to the original owner if the original owner sent the message. * test Adding and additional test that validates several of the fixes in this PR. * test fix Removing the DAHost testfixture that wasn't supposed to be added. Fixing an issue with the NetworkObjectOwnershipTests when running in DAHost mode. It was passing because the DAHost was sending the ChangeOwnershipMessage back to the owner that changed ownership to another client (without the fix for that in this PR it would send the message to the authority/owner which is why the test was passing). * Update com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs Co-authored-by: Dominick * style Updating all instances of k_ServerDeltaForwadingAndNetworkDelivery and replacing with k_ServerDeltaForwardingAndNetworkDelivery --------- Co-authored-by: Dominick --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 + .../Runtime/Core/NetworkBehaviour.cs | 68 +- .../Runtime/Core/NetworkBehaviourUpdater.cs | 18 +- .../Runtime/Core/NetworkObject.cs | 12 +- .../Messages/ChangeOwnershipMessage.cs | 40 +- .../Messages/NetworkVariableDeltaMessage.cs | 308 ++++- .../Collections/NetworkList.cs | 68 +- .../NetworkVariable/NetworkVariable.cs | 95 +- .../NetworkVariable/NetworkVariableBase.cs | 32 + .../Runtime/Spawning/NetworkSpawnManager.cs | 46 +- .../Tests/Runtime/DeferredMessagingTests.cs | 16 +- .../Tests/Runtime/HiddenVariableTests.cs | 14 +- .../NetworkObjectOwnershipTests.cs | 46 +- .../NetworkVariableCollectionsTests.cs | 1024 +++++++++++++++-- 14 files changed, 1608 insertions(+), 185 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f56da05c9c..971c8b5db6 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue with nested `NetworkTransform` components clearing their initial prefab settings when in owner authoritative mode on the server side while using a client-server network topology which resulted in improper synchronization of the nested `NetworkTransform` components. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) +- Fixed issue where a newly synchronizing client would be synchronized with the current `NetworkVariable` values always which could cause issues with collections if there were any pending state updates. Now, when initially synchronizing a client, if a `NetworkVariable` has a pending state update it will serialize the previously known value(s) to the synchronizing client so when the pending updates are sent they aren't duplicate values on the newly connected client side. (#3081) +- Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3081) +- Fixed issue with `NetworkVariable` collections where transferring ownership to another client would not update the new owner's previous value to the most current value which could cause the last/previous added value to be detected as a change when adding or removing an entry (as long as the entry removed was not the last/previously added value). (#3081) +- Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3081) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) - Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075) @@ -29,6 +33,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097) +- Changed `NetworkVariableDeltaMessage` so the server now forwards delta state updates (owner write permission based from a client) to other clients immediately as opposed to keeping a `NetworkVariable` or `NetworkList` dirty and processing them at the end of the frame or potentially on the next network tick. (#3081) + ## [2.0.0] - 2024-09-12 diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index b5d549501a..bcdd25fd95 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -826,6 +826,13 @@ public virtual void OnGainedOwnership() { } internal void InternalOnGainedOwnership() { UpdateNetworkProperties(); + // New owners need to assure any NetworkVariables they have write permissions + // to are updated so the previous and original values are aligned with the + // current value (primarily for collections). + if (OwnerClientId == NetworkManager.LocalClientId) + { + UpdateNetworkVariableOnOwnershipChanged(); + } OnGainedOwnership(); } @@ -1016,9 +1023,14 @@ internal void PreVariableUpdate() internal readonly List NetworkVariableIndexesToReset = new List(); internal readonly HashSet NetworkVariableIndexesToResetSet = new HashSet(); - internal void NetworkVariableUpdate(ulong targetClientId) + /// + /// Determines if a NetworkVariable should have any changes to state sent out + /// + /// target to send the updates to + /// specific to change in ownership + internal void NetworkVariableUpdate(ulong targetClientId, bool forceSend = false) { - if (!CouldHaveDirtyNetworkVariables()) + if (!forceSend && !CouldHaveDirtyNetworkVariables()) { return; } @@ -1069,7 +1081,11 @@ internal void NetworkVariableUpdate(ulong targetClientId) NetworkBehaviourIndex = behaviourIndex, NetworkBehaviour = this, TargetClientId = targetClientId, - DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j] + DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j], + // By sending the network delivery we can forward messages immediately as opposed to processing them + // at the end. While this will send updates to clients that cannot read, the handler will ignore anything + // sent to a client that does not have read permissions. + NetworkDelivery = m_DeliveryTypesForNetworkVariableGroups[j] }; // TODO: Serialization is where the IsDirty flag gets changed. // Messages don't get sent from the server to itself, so if we're host and sending to ourselves, @@ -1114,6 +1130,26 @@ private bool CouldHaveDirtyNetworkVariables() return false; } + /// + /// Invoked on a new client to assure the previous and original values + /// are synchronized with the current known value. + /// + /// + /// Primarily for collections to assure the previous value(s) is/are the + /// same as the current value(s) in order to not re-send already known entries. + /// + internal void UpdateNetworkVariableOnOwnershipChanged() + { + for (int j = 0; j < NetworkVariableFields.Count; j++) + { + // Only invoke OnInitialize on NetworkVariables the owner can write to + if (NetworkVariableFields[j].CanClientWrite(OwnerClientId)) + { + NetworkVariableFields[j].OnInitialize(); + } + } + } + internal void MarkVariablesDirty(bool dirty) { for (int j = 0; j < NetworkVariableFields.Count; j++) @@ -1122,6 +1158,17 @@ internal void MarkVariablesDirty(bool dirty) } } + internal void MarkOwnerReadVariablesDirty() + { + for (int j = 0; j < NetworkVariableFields.Count; j++) + { + if (NetworkVariableFields[j].ReadPerm == NetworkVariableReadPermission.Owner) + { + NetworkVariableFields[j].SetDirty(true); + } + } + } + /// /// Synchronizes by setting only the NetworkVariable field values that the client has permission to read. /// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject) @@ -1172,17 +1219,24 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. writer.WriteValueSafe((ushort)0); var startPos = writer.Position; - NetworkVariableFields[j].WriteField(writer); + // Write the NetworkVariable field value + // WriteFieldSynchronization will write the current value only if there are no pending changes. + // Otherwise, it will write the previous value if there are pending changes since the pending + // changes will be sent shortly after the client's synchronization. + NetworkVariableFields[j].WriteFieldSynchronization(writer); var size = writer.Position - startPos; writer.Seek(writePos); - // Write the NetworkVariable value + // Write the NetworkVariable field value size writer.WriteValueSafe((ushort)size); writer.Seek(startPos + size); } else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology { - // Write the NetworkVariable value - NetworkVariableFields[j].WriteField(writer); + // Write the NetworkVariable field value + // WriteFieldSynchronization will write the current value only if there are no pending changes. + // Otherwise, it will write the previous value if there are pending changes since the pending + // changes will be sent shortly after the client's synchronization. + NetworkVariableFields[j].WriteFieldSynchronization(writer); } } else if (ensureLengthSafety) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 7bf2030647..9062ebf113 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -19,10 +19,15 @@ public class NetworkBehaviourUpdater internal void AddForUpdate(NetworkObject networkObject) { + // Since this is a HashSet, we don't need to worry about duplicate entries m_PendingDirtyNetworkObjects.Add(networkObject); } - internal void NetworkBehaviourUpdate() + /// + /// Sends NetworkVariable deltas + /// + /// internal only, when changing ownership we want to send this before the change in ownership message + internal void NetworkBehaviourUpdate(bool forceSend = false) { #if DEVELOPMENT_BUILD || UNITY_EDITOR m_NetworkBehaviourUpdate.Begin(); @@ -53,7 +58,7 @@ internal void NetworkBehaviourUpdate() // Sync just the variables for just the objects this client sees for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++) { - dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId); + dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId, forceSend); } } } @@ -72,7 +77,7 @@ internal void NetworkBehaviourUpdate() } for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) { - sobj.ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId); + sobj.ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId, forceSend); } } } @@ -85,19 +90,24 @@ internal void NetworkBehaviourUpdate() var behaviour = dirtyObj.ChildNetworkBehaviours[k]; for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++) { + // Set to true for NetworkVariable to ignore duplication of the + // "internal original value" for collections support. + behaviour.NetworkVariableFields[i].NetworkUpdaterCheck = true; if (behaviour.NetworkVariableFields[i].IsDirty() && !behaviour.NetworkVariableIndexesToResetSet.Contains(i)) { behaviour.NetworkVariableIndexesToResetSet.Add(i); behaviour.NetworkVariableIndexesToReset.Add(i); } + // Reset back to false when done + behaviour.NetworkVariableFields[i].NetworkUpdaterCheck = false; } } } // Now, reset all the no-longer-dirty variables foreach (var dirtyobj in m_DirtyNetworkObjects) { - dirtyobj.PostNetworkVariableWrite(); + dirtyobj.PostNetworkVariableWrite(forceSend); // Once done processing, we set the previous owner id to the current owner id dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index d1582c6f03..3ccd8ea72e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2445,6 +2445,14 @@ internal void MarkVariablesDirty(bool dirty) } } + internal void MarkOwnerReadVariablesDirty() + { + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + ChildNetworkBehaviours[i].MarkOwnerReadVariablesDirty(); + } + } + // NGO currently guarantees that the client will receive spawn data for all objects in one network tick. // Children may arrive before their parents; when they do they are stored in OrphanedChildren and then // resolved when their parents arrived. Because we don't send a partial list of spawns (yet), something @@ -2771,11 +2779,11 @@ public void Deserialize(FastBufferReader reader) } } - internal void PostNetworkVariableWrite() + internal void PostNetworkVariableWrite(bool forced = false) { for (int k = 0; k < ChildNetworkBehaviours.Count; k++) { - ChildNetworkBehaviours[k].PostNetworkVariableWrite(); + ChildNetworkBehaviours[k].PostNetworkVariableWrite(forced); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 99010be29b..d418789f4d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -9,7 +9,6 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem public ulong NetworkObjectId; public ulong OwnerClientId; - // DANGOEXP TODO: Remove these notes or change their format // SERVICE NOTES: // When forwarding the message to clients on the CMB Service side, // you can set the ClientIdCount to 0 and skip writing the ClientIds. @@ -258,15 +257,18 @@ public void Handle(ref NetworkContext context) continue; } - // If ownership is changing and this is not an ownership request approval then ignore the OnwerClientId + // If ownership is changing and this is not an ownership request approval then ignore the SenderId + if (OwnershipIsChanging && !RequestApproved && context.SenderId == clientId) + { + continue; + } + // If it is just updating flags then ignore sending to the owner // If it is a request or approving request, then ignore the RequestClientId - if ((OwnershipIsChanging && !RequestApproved && OwnerClientId == clientId) || (OwnershipFlagsUpdate && clientId == OwnerClientId) - || ((RequestOwnership || RequestApproved) && clientId == RequestClientId)) + if ((OwnershipFlagsUpdate && clientId == OwnerClientId) || ((RequestOwnership || RequestApproved) && clientId == RequestClientId)) { continue; } - networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, clientId); } } @@ -327,10 +329,12 @@ private void HandleOwnershipChange(ref NetworkContext context) var networkManager = (NetworkManager)context.SystemOwner; var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; - // DANGO-TODO: This probably shouldn't be allowed to happen. + // Sanity check that we are not sending duplicated change ownership messages if (networkObject.OwnerClientId == OwnerClientId) { - UnityEngine.Debug.LogWarning($"Unnecessary ownership changed message for {NetworkObjectId}"); + UnityEngine.Debug.LogError($"Unnecessary ownership changed message for {NetworkObjectId}."); + // Ignore the message + return; } var originalOwner = networkObject.OwnerClientId; @@ -347,12 +351,6 @@ private void HandleOwnershipChange(ref NetworkContext context) networkObject.InvokeBehaviourOnLostOwnership(); } - // We are new owner or (client-server) or running in distributed authority mode - if (OwnerClientId == networkManager.LocalClientId || networkManager.DistributedAuthorityMode) - { - networkObject.InvokeBehaviourOnGainedOwnership(); - } - // If in distributed authority mode if (networkManager.DistributedAuthorityMode) { @@ -374,6 +372,22 @@ private void HandleOwnershipChange(ref NetworkContext context) } } + // We are new owner or (client-server) or running in distributed authority mode + if (OwnerClientId == networkManager.LocalClientId || networkManager.DistributedAuthorityMode) + { + networkObject.InvokeBehaviourOnGainedOwnership(); + } + + + if (originalOwner == networkManager.LocalClientId && !networkManager.DistributedAuthorityMode) + { + // Mark any owner read variables as dirty + networkObject.MarkOwnerReadVariablesDirty(); + // Immediately queue any pending deltas and order the message before the + // change in ownership message. + networkManager.BehaviourUpdater.NetworkBehaviourUpdate(true); + } + // Always invoke ownership change notifications networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index eb6050e8aa..8db084cd7d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Unity.Collections; namespace Unity.Netcode @@ -10,9 +11,22 @@ namespace Unity.Netcode /// serialization. This is due to the generally amorphous nature of network variable /// deltas, since they're all driven by custom virtual method overloads. /// + /// + /// Version 1: + /// This version -does not- use the "KeepDirty" approach. Instead, the server will forward any state updates + /// to the connected clients that are not the sender or the server itself. Each NetworkVariable state update + /// included, on a per client basis, is first validated that the client can read the NetworkVariable before + /// being added to the m_ForwardUpdates table. + /// Version 0: + /// The original version uses the "KeepDirty" approach in a client-server network topology where the server + /// proxies state updates by "keeping the NetworkVariable(s) dirty" so it will send state updates + /// at the end of the frame (but could delay until the next tick). + /// internal struct NetworkVariableDeltaMessage : INetworkMessage { - public int Version => 0; + private const int k_ServerDeltaForwardingAndNetworkDelivery = 1; + public int Version => k_ServerDeltaForwardingAndNetworkDelivery; + public ulong NetworkObjectId; public ushort NetworkBehaviourIndex; @@ -21,10 +35,62 @@ internal struct NetworkVariableDeltaMessage : INetworkMessage public ulong TargetClientId; public NetworkBehaviour NetworkBehaviour; + public NetworkDelivery NetworkDelivery; + private FastBufferReader m_ReceivedNetworkVariableData; + private bool m_ForwardingMessage; + + private int m_ReceivedMessageVersion; + private const string k_Name = "NetworkVariableDeltaMessage"; + private Dictionary> m_ForwardUpdates; + + private List m_UpdatedNetworkVariables; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteNetworkVariable(ref FastBufferWriter writer, ref NetworkVariableBase networkVariable, bool distributedAuthorityMode, bool ensureNetworkVariableLengthSafety, int nonfragmentedSize, int fragmentedSize) + { + if (ensureNetworkVariableLengthSafety) + { + var tempWriter = new FastBufferWriter(nonfragmentedSize, Allocator.Temp, fragmentedSize); + networkVariable.WriteDelta(tempWriter); + BytePacker.WriteValueBitPacked(writer, tempWriter.Length); + + if (!writer.TryBeginWrite(tempWriter.Length)) + { + throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); + } + + tempWriter.CopyTo(writer); + } + else + { + // TODO: Determine if we need to remove this with the 6.1 service updates + if (distributedAuthorityMode) + { + var size_marker = writer.Position; + writer.WriteValueSafe(0); + var start_marker = writer.Position; + networkVariable.WriteDelta(writer); + var end_marker = writer.Position; + writer.Seek(size_marker); + var size = end_marker - start_marker; + if (size == 0) + { + UnityEngine.Debug.LogError($"Invalid write size of zero!"); + } + writer.WriteValueSafe((ushort)size); + writer.Seek(end_marker); + } + else + { + networkVariable.WriteDelta(writer); + } + } + } + public void Serialize(FastBufferWriter writer, int targetVersion) { if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) @@ -34,10 +100,67 @@ public void Serialize(FastBufferWriter writer, int targetVersion) var obj = NetworkBehaviour.NetworkObject; var networkManager = obj.NetworkManagerOwner; + var typeName = NetworkBehaviour.__getTypeName(); + var nonFragmentedMessageMaxSize = networkManager.MessageManager.NonFragmentedMessageMaxSize; + var fragmentedMessageMaxSize = networkManager.MessageManager.FragmentedMessageMaxSize; + var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; + var distributedAuthorityMode = networkManager.DistributedAuthorityMode; BytePacker.WriteValueBitPacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex); - if (networkManager.DistributedAuthorityMode) + + // If using k_IncludeNetworkDelivery version, then we want to write the network delivery used and if we + // are forwarding state updates then serialize any NetworkVariable states specific to this client. + if (targetVersion >= k_ServerDeltaForwardingAndNetworkDelivery) + { + writer.WriteValueSafe(NetworkDelivery); + // If we are forwarding the message, then proceed to forward state updates specific to the targeted client + if (m_ForwardingMessage) + { + // DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode) + { + writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count); + } + + for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++) + { + var startingSize = writer.Length; + var networkVariable = NetworkBehaviour.NetworkVariableFields[i]; + var shouldWrite = m_ForwardUpdates[TargetClientId].Contains(i); + + // This var does not belong to the currently iterating delivery group. + if (distributedAuthorityMode) + { + if (!shouldWrite) + { + writer.WriteValueSafe(0); + } + } + else if (ensureNetworkVariableLengthSafety) + { + if (!shouldWrite) + { + BytePacker.WriteValueBitPacked(writer, (ushort)0); + } + } + else + { + writer.WriteValueSafe(shouldWrite); + } + + if (shouldWrite) + { + WriteNetworkVariable(ref writer, ref networkVariable, distributedAuthorityMode, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize); + networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize); + } + } + return; + } + } + + // DANGO TODO: Remove this when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode) { writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count); } @@ -46,12 +169,12 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { if (!DeliveryMappedNetworkVariableIndex.Contains(i)) { - // This var does not belong to the currently iterating delivery group. - if (networkManager.DistributedAuthorityMode) + // DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode) { writer.WriteValueSafe(0); } - else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + else if (ensureNetworkVariableLengthSafety) { BytePacker.WriteValueBitPacked(writer, (ushort)0); } @@ -88,14 +211,15 @@ public void Serialize(FastBufferWriter writer, int targetVersion) shouldWrite = false; } - if (networkManager.DistributedAuthorityMode) + // DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode) { if (!shouldWrite) { writer.WriteValueSafe(0); } } - else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + else if (ensureNetworkVariableLengthSafety) { if (!shouldWrite) { @@ -109,53 +233,22 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (shouldWrite) { - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) - { - var tempWriter = new FastBufferWriter(networkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, networkManager.MessageManager.FragmentedMessageMaxSize); - NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter); - BytePacker.WriteValueBitPacked(writer, tempWriter.Length); - - if (!writer.TryBeginWrite(tempWriter.Length)) - { - throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); - } - - tempWriter.CopyTo(writer); - } - else - { - if (networkManager.DistributedAuthorityMode) - { - var size_marker = writer.Position; - writer.WriteValueSafe(0); - var start_marker = writer.Position; - networkVariable.WriteDelta(writer); - var end_marker = writer.Position; - writer.Seek(size_marker); - var size = end_marker - start_marker; - writer.WriteValueSafe((ushort)size); - writer.Seek(end_marker); - } - else - { - networkVariable.WriteDelta(writer); - } - } - networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( - TargetClientId, - obj, - networkVariable.Name, - NetworkBehaviour.__getTypeName(), - writer.Length - startingSize); + WriteNetworkVariable(ref writer, ref networkVariable, distributedAuthorityMode, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize); + networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize); } } } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { + m_ReceivedMessageVersion = receivedMessageVersion; ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex); - + // If we are using the k_IncludeNetworkDelivery message version, then read the NetworkDelivery used + if (receivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery) + { + reader.ReadValueSafe(out NetworkDelivery); + } m_ReceivedNetworkVariableData = reader; return true; @@ -167,7 +260,12 @@ public void Handle(ref NetworkContext context) if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject)) { + var distributedAuthorityMode = networkManager.DistributedAuthorityMode; + var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); + var isServerAndDeltaForwarding = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery && networkManager.IsServer; + var markNetworkVariableDirty = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery ? false : networkManager.IsServer; + m_UpdatedNetworkVariables = new List(); if (networkBehaviour == null) { @@ -178,7 +276,8 @@ public void Handle(ref NetworkContext context) } else { - if (networkManager.DistributedAuthorityMode) + // DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode) { m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableCount); if (variableCount != networkBehaviour.NetworkVariableFields.Count) @@ -187,10 +286,30 @@ public void Handle(ref NetworkContext context) } } + // (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable + // updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded + // to the client. This creates a list of all remaining connected clients that could have updates applied. + if (isServerAndDeltaForwarding) + { + m_ForwardUpdates = new Dictionary>(); + foreach (var clientId in networkManager.ConnectedClientsIds) + { + if (clientId == context.SenderId || clientId == networkManager.LocalClientId || !networkObject.Observers.Contains(clientId)) + { + continue; + } + m_ForwardUpdates.Add(clientId, new List()); + } + } + + // Update NetworkVariable Fields for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++) { int varSize = 0; - if (networkManager.DistributedAuthorityMode) + var networkVariable = networkBehaviour.NetworkVariableFields[i]; + + // DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode) { m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableSize); varSize = variableSize; @@ -200,10 +319,9 @@ public void Handle(ref NetworkContext context) continue; } } - else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) + else if (ensureNetworkVariableLengthSafety) { ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize); - if (varSize == 0) { continue; @@ -218,8 +336,6 @@ public void Handle(ref NetworkContext context) } } - var networkVariable = networkBehaviour.NetworkVariableFields[i]; - if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId)) { // we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server @@ -247,13 +363,58 @@ public void Handle(ref NetworkContext context) NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}"); NetworkLog.LogError($"[{networkVariable.GetType().Name}]"); } - return; } int readStartPos = m_ReceivedNetworkVariableData.Position; - // Read Delta so we also notify any subscribers to a change in the NetworkVariable - networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer); + // DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode || ensureNetworkVariableLengthSafety) + { + var remainingBufferSize = m_ReceivedNetworkVariableData.Length - m_ReceivedNetworkVariableData.Position; + if (varSize > (remainingBufferSize)) + { + UnityEngine.Debug.LogError($"[{networkBehaviour.name}][Delta State Read Error] Expecting to read {varSize} but only {remainingBufferSize} remains!"); + return; + } + } + + // Added a try catch here to assure any failure will only fail on this one message and not disrupt the stack + try + { + // Read the delta + networkVariable.ReadDelta(m_ReceivedNetworkVariableData, markNetworkVariableDirty); + + // Add the NetworkVariable field index so we can invoke the PostDeltaRead + m_UpdatedNetworkVariables.Add(i); + } + catch (Exception ex) + { + UnityEngine.Debug.LogException(ex); + return; + } + + // (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable + // updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded + // to the client. This happens once the server is finished processing all state updates for this message. + if (isServerAndDeltaForwarding) + { + foreach (var forwardEntry in m_ForwardUpdates) + { + // Only track things that the client can read + if (networkVariable.CanClientRead(forwardEntry.Key)) + { + // If the object is about to be shown to the client then don't send an update as it will + // send a full update when shown. + if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(forwardEntry.Key) && + networkManager.SpawnManager.ObjectsToShowToClient[forwardEntry.Key] + .Contains(networkObject)) + { + continue; + } + forwardEntry.Value.Add(i); + } + } + } networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( context.SenderId, @@ -262,7 +423,8 @@ public void Handle(ref NetworkContext context) networkBehaviour.__getTypeName(), context.MessageSize); - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety || networkManager.DistributedAuthorityMode) + // DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff + if (distributedAuthorityMode || ensureNetworkVariableLengthSafety) { if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize)) { @@ -284,6 +446,40 @@ public void Handle(ref NetworkContext context) } } } + + // If we are using the version of this message that includes network delivery, then + // forward this update to all connected clients (other than the sender and the server). + if (isServerAndDeltaForwarding) + { + var message = new NetworkVariableDeltaMessage() + { + NetworkBehaviour = networkBehaviour, + NetworkBehaviourIndex = NetworkBehaviourIndex, + NetworkObjectId = NetworkObjectId, + m_ForwardingMessage = true, + m_ForwardUpdates = m_ForwardUpdates, + }; + + foreach (var forwardEntry in m_ForwardUpdates) + { + // Only forward updates to any client that has visibility to the state updates included in this message + if (forwardEntry.Value.Count > 0) + { + message.TargetClientId = forwardEntry.Key; + networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery, forwardEntry.Key); + } + } + } + + // This should be always invoked (client & server) to assure the previous values are set + // !! IMPORTANT ORDER OF OPERATIONS !! (Has to happen after forwarding deltas) + // When a server forwards delta updates to connected clients, it needs to preserve the previous value + // until it is done serializing all valid NetworkVariable field deltas (relative to each client). This + // is invoked after it is done forwarding the deltas. + foreach (var fieldIndex in m_UpdatedNetworkVariables) + { + networkBehaviour.NetworkVariableFields[fieldIndex].PostDeltaRead(); + } } } else diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 130cf4e938..203632c184 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -177,6 +177,13 @@ public override void ReadField(FastBufferReader reader) /// public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { + /// This is only invoked by and the only time + /// keepDirtyDelta is set is when it is the server processing. To be able to handle previous + /// versions, we use IsServer to keep the dirty states received and the keepDirtyDelta to + /// actually mark this as dirty and add it to the list of s to + /// be updated. With the forwarding of deltas being handled by , + /// once all clients have been forwarded the dirty events, we clear them by invoking . + var isServer = m_NetworkManager.IsServer; reader.ReadValueSafe(out ushort deltaCount); for (int i = 0; i < deltaCount; i++) { @@ -199,7 +206,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) }); } - if (keepDirtyDelta) + if (isServer) { m_DirtyEvents.Add(new NetworkListEvent() { @@ -207,7 +214,11 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = m_List.Length - 1, Value = m_List[m_List.Length - 1] }); - MarkNetworkObjectDirty(); + // Preserve the legacy way of handling this + if (keepDirtyDelta) + { + MarkNetworkObjectDirty(); + } } } break; @@ -237,7 +248,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) }); } - if (keepDirtyDelta) + if (isServer) { m_DirtyEvents.Add(new NetworkListEvent() { @@ -245,7 +256,11 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = index, Value = m_List[index] }); - MarkNetworkObjectDirty(); + // Preserve the legacy way of handling this + if (keepDirtyDelta) + { + MarkNetworkObjectDirty(); + } } } break; @@ -271,7 +286,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) }); } - if (keepDirtyDelta) + if (isServer) { m_DirtyEvents.Add(new NetworkListEvent() { @@ -279,7 +294,11 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = index, Value = value }); - MarkNetworkObjectDirty(); + // Preserve the legacy way of handling this + if (keepDirtyDelta) + { + MarkNetworkObjectDirty(); + } } } break; @@ -299,7 +318,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) }); } - if (keepDirtyDelta) + if (isServer) { m_DirtyEvents.Add(new NetworkListEvent() { @@ -307,7 +326,11 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Index = index, Value = value }); - MarkNetworkObjectDirty(); + // Preserve the legacy way of handling this + if (keepDirtyDelta) + { + MarkNetworkObjectDirty(); + } } } break; @@ -335,7 +358,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) }); } - if (keepDirtyDelta) + if (isServer) { m_DirtyEvents.Add(new NetworkListEvent() { @@ -344,7 +367,11 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) Value = value, PreviousValue = previousValue }); - MarkNetworkObjectDirty(); + // Preserve the legacy way of handling this + if (keepDirtyDelta) + { + MarkNetworkObjectDirty(); + } } } break; @@ -361,13 +388,18 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) }); } - if (keepDirtyDelta) + if (isServer) { m_DirtyEvents.Add(new NetworkListEvent() { Type = eventType }); - MarkNetworkObjectDirty(); + + // Preserve the legacy way of handling this + if (keepDirtyDelta) + { + MarkNetworkObjectDirty(); + } } } break; @@ -381,6 +413,18 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) } } + /// + /// + /// For NetworkList, we just need to reset dirty if a server has read deltas + /// + internal override void PostDeltaRead() + { + if (m_NetworkManager.IsServer) + { + ResetDirty(); + } + } + /// public IEnumerator GetEnumerator() { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index a98197df5f..16ca42ad91 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -41,6 +41,7 @@ public override void OnInitialize() base.OnInitialize(); m_HasPreviousValue = true; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_InternalOriginalValue); NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); } @@ -58,6 +59,7 @@ public NetworkVariable(T value = default, : base(readPerm, writePerm) { m_InternalValue = value; + m_InternalOriginalValue = default; // Since we start with IsDirty = true, this doesn't need to be duplicated // right away. It won't get read until after ResetDirty() is called, and // the duplicate will be made there. Avoiding calling @@ -76,6 +78,7 @@ public void Reset(T value = default) if (m_NetworkBehaviour == null || m_NetworkBehaviour != null && !m_NetworkBehaviour.NetworkObject.IsSpawned) { m_InternalValue = value; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_InternalOriginalValue); m_PreviousValue = default; } } @@ -86,6 +89,12 @@ public void Reset(T value = default) [SerializeField] private protected T m_InternalValue; + // The introduction of standard .NET collections caused an issue with permissions since there is no way to detect changes in the + // collection without doing a full comparison. While this approach does consume more memory per collection instance, it is the + // lowest risk approach to resolving the issue where a client with no write permissions could make changes to a collection locally + // which can cause a myriad of issues. + private protected T m_InternalOriginalValue; + private protected T m_PreviousValue; private bool m_HasPreviousValue; @@ -116,6 +125,7 @@ public virtual T Value { T previousValue = m_InternalValue; m_InternalValue = value; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_InternalOriginalValue); SetDirty(true); m_IsDisposed = false; OnValueChanged?.Invoke(previousValue, m_InternalValue); @@ -136,6 +146,17 @@ public bool CheckDirtyState(bool forceCheck = false) { var isDirty = base.IsDirty(); + // A client without permissions invoking this method should only check to assure the current value is equal to the last known current value + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId)) + { + // If modifications are detected, then revert back to the last known current value + if (!NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref m_InternalOriginalValue)) + { + NetworkVariableSerialization.Duplicate(m_InternalOriginalValue, ref m_InternalValue); + } + return false; + } + // Compare the previous with the current if not dirty or forcing a check. if ((!isDirty || forceCheck) && !NetworkVariableSerialization.AreEqual(ref m_PreviousValue, ref m_InternalValue)) { @@ -166,6 +187,7 @@ public override void Dispose() } m_InternalValue = default; + m_InternalOriginalValue = default; if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable) { m_HasPreviousValue = false; @@ -188,6 +210,13 @@ public override void Dispose() /// Whether or not the container is dirty public override bool IsDirty() { + // If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert + // to the original collection value prior to applying updates (primarily for collections). + if (!NetworkUpdaterCheck && m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId) && !NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref m_InternalOriginalValue)) + { + NetworkVariableSerialization.Duplicate(m_InternalOriginalValue, ref m_InternalValue); + return true; + } // For most cases we can use the dirty flag. // This doesn't work for cases where we're wrapping more complex types // like INetworkSerializable, NativeList, NativeArray, etc. @@ -199,11 +228,11 @@ public override bool IsDirty() return true; } + var dirty = !NetworkVariableSerialization.AreEqual(ref m_PreviousValue, ref m_InternalValue); // Cache the dirty value so we don't perform this again if we already know we're dirty // Unfortunately we can't cache the NOT dirty state, because that might change // in between to checks... but the DIRTY state won't change until ResetDirty() // is called. - var dirty = !NetworkVariableSerialization.AreEqual(ref m_PreviousValue, ref m_InternalValue); SetDirty(dirty); return dirty; } @@ -221,6 +250,8 @@ public override void ResetDirty() { m_HasPreviousValue = true; NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); + // Once updated, assure the original current value is updated for future comparison purposes + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_InternalOriginalValue); } base.ResetDirty(); } @@ -241,16 +272,20 @@ public override void WriteDelta(FastBufferWriter writer) /// Whether or not the container should keep the dirty delta, or mark the delta as consumed public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { - // In order to get managed collections to properly have a previous and current value, we have to - // duplicate the collection at this point before making any modifications to the current. - m_HasPreviousValue = true; - NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); + // If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert + // to the original collection value prior to applying updates (primarily for collections). + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId) && !NetworkVariableSerialization.AreEqual(ref m_InternalOriginalValue, ref m_InternalValue)) + { + NetworkVariableSerialization.Duplicate(m_InternalOriginalValue, ref m_InternalValue); + } + NetworkVariableSerialization.ReadDelta(reader, ref m_InternalValue); - // todo: // keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients // In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit // would be stored in different fields + // LEGACY NOTE: This is only to handle NetworkVariableDeltaMessage Version 0 connections. The updated + // NetworkVariableDeltaMessage no longer uses this approach. if (keepDirtyDelta) { SetDirty(true); @@ -259,10 +294,43 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue); } + /// + /// This should be always invoked (client & server) to assure the previous values are set + /// !! IMPORTANT !! + /// When a server forwards delta updates to connected clients, it needs to preserve the previous dirty value(s) + /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked + /// after it is done forwarding the deltas at the end of the method. + /// + internal override void PostDeltaRead() + { + // In order to get managed collections to properly have a previous and current value, we have to + // duplicate the collection at this point before making any modifications to the current. + m_HasPreviousValue = true; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); + // Once updated, assure the original current value is updated for future comparison purposes + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_InternalOriginalValue); + } + /// public override void ReadField(FastBufferReader reader) { + // If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert + // to the original collection value prior to applying updates (primarily for collections). + if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId) && !NetworkVariableSerialization.AreEqual(ref m_InternalOriginalValue, ref m_InternalValue)) + { + NetworkVariableSerialization.Duplicate(m_InternalOriginalValue, ref m_InternalValue); + } + NetworkVariableSerialization.Read(reader, ref m_InternalValue); + // In order to get managed collections to properly have a previous and current value, we have to + // duplicate the collection at this point before making any modifications to the current. + // We duplicate the final value after the read (for ReadField ONLY) so the previous value is at par + // with the current value (since this is only invoked when initially synchronizing). + m_HasPreviousValue = true; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); + + // Once updated, assure the original current value is updated for future comparison purposes + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_InternalOriginalValue); } /// @@ -270,5 +338,20 @@ public override void WriteField(FastBufferWriter writer) { NetworkVariableSerialization.Write(writer, ref m_InternalValue); } + + internal override void WriteFieldSynchronization(FastBufferWriter writer) + { + // If we have a pending update, then synchronize the client with the previously known + // value since the updated version will be sent on the next tick or next time it is + // set to be updated + if (base.IsDirty() && m_HasPreviousValue) + { + NetworkVariableSerialization.Write(writer, ref m_PreviousValue); + } + else + { + base.WriteFieldSynchronization(writer); + } + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 75ce48aa1c..88023978ac 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -251,6 +251,12 @@ public virtual void ResetDirty() m_IsDirty = false; } + /// + /// Only used during the NetworkBehaviourUpdater pass and only used for NetworkVariable. + /// This is to bypass duplication of the "original internal value" for collections. + /// + internal bool NetworkUpdaterCheck; + /// /// Gets Whether or not the container is dirty /// @@ -341,6 +347,32 @@ internal ulong OwnerClientId() /// Whether or not the delta should be kept as dirty or consumed public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta); + /// + /// This should be always invoked (client & server) to assure the previous values are set + /// !! IMPORTANT !! + /// When a server forwards delta updates to connected clients, it needs to preserve the previous dirty value(s) + /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked + /// after it is done forwarding the deltas at the end of the method. + /// + internal virtual void PostDeltaRead() + { + } + + /// + /// There are scenarios, specifically with collections, where a client could be synchronizing and + /// some NetworkVariables have pending updates. To avoid duplicating entries, this is invoked only + /// when sending the full synchronization information. + /// + /// + /// Derrived classes should send the previous value for synchronization so when the updated value + /// is sent (after synchronizing the client) it will apply the updates. + /// + /// + internal virtual void WriteFieldSynchronization(FastBufferWriter writer) + { + WriteField(writer); + } + /// /// Virtual implementation /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index daac9dea21..c17e05653b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -434,8 +434,31 @@ internal void RemoveOwnership(NetworkObject networkObject) ChangeOwnership(networkObject, NetworkManager.ServerClientId, true); } + private Dictionary m_LastChangeInOwnership = new Dictionary(); + private const int k_MaximumTickOwnershipChangeMultiplier = 6; + internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool isAuthorized, bool isRequestApproval = false) { + // For client-server: + // If ownership changes faster than the latency between the client-server and there are NetworkVariables being updated during ownership changes, + // then notify the user they could potentially lose state updates if developer logging is enabled. + if (!NetworkManager.DistributedAuthorityMode && m_LastChangeInOwnership.ContainsKey(networkObject.NetworkObjectId) && m_LastChangeInOwnership[networkObject.NetworkObjectId] > Time.realtimeSinceStartup) + { + var hasNetworkVariables = false; + for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) + { + hasNetworkVariables = networkObject.ChildNetworkBehaviours[i].NetworkVariableFields.Count > 0; + if (hasNetworkVariables) + { + break; + } + } + if (hasNetworkVariables && NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarningServer($"[Rapid Ownership Change Detected][Potential Loss in State] Detected a rapid change in ownership that exceeds a frequency less than {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate! Provide at least {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate between ownership changes to avoid NetworkVariable state loss."); + } + } + if (NetworkManager.DistributedAuthorityMode) { // If are not authorized and this is not an approved ownership change, then check to see if we can change ownership @@ -508,15 +531,21 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool // Always notify locally on the server when ownership is lost networkObject.InvokeBehaviourOnLostOwnership(); - networkObject.MarkVariablesDirty(true); - NetworkManager.BehaviourUpdater.AddForUpdate(networkObject); - // Authority adds entries for all client ownership UpdateOwnershipTable(networkObject, networkObject.OwnerClientId); // Always notify locally on the server when a new owner is assigned networkObject.InvokeBehaviourOnGainedOwnership(); + if (networkObject.PreviousOwnerId == NetworkManager.LocalClientId) + { + // Mark any owner read variables as dirty + networkObject.MarkOwnerReadVariablesDirty(); + // Immediately queue any pending deltas and order the message before the + // change in ownership message. + NetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(true); + } + var size = 0; if (NetworkManager.DistributedAuthorityMode) { @@ -580,6 +609,17 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool /// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership /// change can be sent from NetworkBehaviours that override the networkObject.InvokeOwnershipChanged(networkObject.PreviousOwnerId, clientId); + + // Keep track of the ownership change frequency to assure a user is not exceeding changes faster than 2x the current Tick Rate. + if (!NetworkManager.DistributedAuthorityMode) + { + if (!m_LastChangeInOwnership.ContainsKey(networkObject.NetworkObjectId)) + { + m_LastChangeInOwnership.Add(networkObject.NetworkObjectId, 0.0f); + } + var tickFrequency = 1.0f / NetworkManager.NetworkConfig.TickRate; + m_LastChangeInOwnership[networkObject.NetworkObjectId] = Time.realtimeSinceStartup + (tickFrequency * k_MaximumTickOwnershipChangeMultiplier); + } } internal bool HasPrefab(NetworkObject.SceneObject sceneObject) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index cda0dd69e6..bc97fd7b31 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -670,7 +670,7 @@ public void WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnS serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - WaitForAllClientsToReceive(); + WaitForAllClientsToReceive(); foreach (var client in m_ClientNetworkManagers) { @@ -678,9 +678,9 @@ public void WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnS Assert.IsTrue(manager.DeferMessageCalled); Assert.IsFalse(manager.ProcessTriggersCalled); - Assert.AreEqual(4, manager.DeferredMessageCountTotal()); - Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn)); - Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); + Assert.AreEqual(3, manager.DeferredMessageCountTotal()); + Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn)); + Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab)); AddPrefabsToClient(client); } @@ -812,7 +812,7 @@ public void WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_AddingTh serverObject.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - WaitForAllClientsToReceive(); + WaitForAllClientsToReceive(); // Validate messages are deferred and pending foreach (var client in m_ClientNetworkManagers) @@ -821,10 +821,10 @@ public void WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_AddingTh Assert.IsTrue(manager.DeferMessageCalled); Assert.IsFalse(manager.ProcessTriggersCalled); - Assert.AreEqual(5, manager.DeferredMessageCountTotal()); + Assert.AreEqual(4, manager.DeferredMessageCountTotal()); - Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn)); - Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); + Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn)); + Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent().NetworkObjectId)); Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab)); Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent().GlobalObjectIdHash)); AddPrefabsToClient(client); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs index f110ed10aa..94b44cf230 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/HiddenVariableTests.cs @@ -59,13 +59,13 @@ public override void OnNetworkDespawn() public void Changed(int before, int after) { - VerboseDebug($"Value changed from {before} to {after} on {NetworkManager.LocalClientId}"); + VerboseDebug($"[Client-{NetworkManager.LocalClientId}][{name}][MyNetworkVariable] Value changed from {before} to {after}"); ValueOnClient[NetworkManager.LocalClientId] = after; } public void ListChanged(NetworkListEvent listEvent) { - Debug.Log($"ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}"); - Debug.Assert(ExpectedSize == MyNetworkList.Count); + VerboseDebug($"[Client-{NetworkManager.LocalClientId}][{name}][MyNetworkList] ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}"); + Debug.Assert(ExpectedSize == MyNetworkList.Count, $"[{name}] List change failure! Expected Count: {ExpectedSize} Actual Count:{MyNetworkList.Count}"); } } @@ -185,10 +185,13 @@ public IEnumerator HiddenVariableTest() var otherClient = m_ServerNetworkManager.ConnectedClientsList[2]; m_NetSpawnedObject = SpawnObject(m_TestNetworkPrefab, m_ClientNetworkManagers[1]).GetComponent(); - yield return RefreshGameObects(4); + yield return RefreshGameObects(NumberOfClients); // === Check spawn occurred yield return WaitForSpawnCount(NumberOfClients + 1); + + AssertOnTimeout($"Timed out waiting for all clients to spawn {m_NetSpawnedObject.name}"); + Debug.Assert(HiddenVariableObject.SpawnCount == NumberOfClients + 1); VerboseDebug("Objects spawned"); @@ -205,7 +208,6 @@ public IEnumerator HiddenVariableTest() // ==== Hide our object to a different client HiddenVariableObject.ExpectedSize = 2; m_NetSpawnedObject.NetworkHide(otherClient.ClientId); - currentValueSet = 3; m_NetSpawnedObject.GetComponent().MyNetworkVariable.Value = currentValueSet; m_NetSpawnedObject.GetComponent().MyNetworkList.Add(currentValueSet); @@ -222,7 +224,7 @@ public IEnumerator HiddenVariableTest() VerboseDebug("Object spawned"); // ==== We need a refresh for the newly re-spawned object - yield return RefreshGameObects(4); + yield return RefreshGameObects(NumberOfClients); currentValueSet = 4; m_NetSpawnedObject.GetComponent().MyNetworkVariable.Value = currentValueSet; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index faf4402a13..bda9d15fcb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -265,6 +265,8 @@ bool WaitForClientsToSpawnNetworkObject() // After the 1st client has been given ownership to the object, this will be used to make sure each previous owner properly received the remove ownership message var previousClientComponent = (NetworkObjectOwnershipComponent)null; + var networkManagersDAMode = new List(); + for (int clientIndex = 0; clientIndex < NumberOfClients; clientIndex++) { clientObject = clientObjects[clientIndex]; @@ -322,6 +324,21 @@ bool WaitForClientsToSpawnNetworkObject() // In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service) if (m_DistributedAuthority) { + // In distributed authority, we have to clear out the NetworkManager instances as this changes relative to authority. + networkManagersDAMode.Clear(); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (clientNetworkManager.LocalClientId == clientObject.OwnerClientId) + { + continue; + } + networkManagersDAMode.Add(clientNetworkManager); + } + + if (!UseCMBService() && clientObject.OwnerClientId != m_ServerNetworkManager.LocalClientId) + { + networkManagersDAMode.Add(m_ServerNetworkManager); + } clientObject.ChangeOwnership(NetworkManager.ServerClientId); } else @@ -330,7 +347,18 @@ bool WaitForClientsToSpawnNetworkObject() } } - yield return WaitForConditionOrTimeOut(ownershipMessageHooks); + if (m_DistributedAuthority) + { + // We use an alternate method (other than message hooks) to verify each client received the ownership message since message hooks becomes problematic when you need + // to make dynamic changes to your targets. + yield return WaitForConditionOrTimeOut(() => OwnershipChangedOnAllTargetedClients(networkManagersDAMode, clientObject.NetworkObjectId, NetworkManager.ServerClientId)); + } + else + { + yield return WaitForConditionOrTimeOut(ownershipMessageHooks); + } + + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all clients to receive the {nameof(ChangeOwnershipMessage)} message (back to server)."); Assert.That(serverComponent.OnGainedOwnershipFired); @@ -351,6 +379,22 @@ bool WaitForClientsToSpawnNetworkObject() serverComponent.ResetFlags(); } + private bool OwnershipChangedOnAllTargetedClients(List networkManagers, ulong networkObjectId, ulong expectedOwner) + { + foreach (var networkManager in networkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + return false; + } + if (networkManager.SpawnManager.SpawnedObjects[networkObjectId].OwnerClientId != expectedOwner) + { + return false; + } + } + return true; + } + private const int k_NumberOfSpawnedObjects = 5; private bool AllClientsHaveCorrectObjectCount() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs index 7d7e785575..0c8a2ffdf9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs @@ -5,12 +5,14 @@ using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; using UnityEngine.TestTools; using Random = UnityEngine.Random; namespace Unity.Netcode.RuntimeTests { /// + /// Client-Server only test /// Validates using managed collections with NetworkVariable. /// Managed Collections Tested: /// - List @@ -18,24 +20,23 @@ namespace Unity.Netcode.RuntimeTests /// - HashSet /// This also does some testing on nested collections, but does /// not test every possible combination. - /// - [TestFixture(HostOrServer.Host, CollectionTypes.List)] - [TestFixture(HostOrServer.Server, CollectionTypes.List)] + ///
+ [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] public class NetworkVariableCollectionsTests : NetcodeIntegrationTest { - public enum CollectionTypes - { - Dictionary, - List, - } - protected override int NumberOfClients => 2; - private CollectionTypes m_CollectionType; + private bool m_EnableDebug; - public NetworkVariableCollectionsTests(HostOrServer hostOrServer, CollectionTypes collectionType) : base(hostOrServer) + public NetworkVariableCollectionsTests(HostOrServer hostOrServer) : base(hostOrServer) { - m_CollectionType = collectionType; + m_EnableDebug = false; + } + + protected override bool OnSetVerboseDebug() + { + return m_EnableDebug; } protected override IEnumerator OnSetup() @@ -50,15 +51,21 @@ protected override IEnumerator OnSetup() return base.OnSetup(); } + private void AddPlayerComponent() where T : ListTestHelperBase + { + var component = m_PlayerPrefab.AddComponent(); + component.SetDebugMode(m_EnableDebug); + } + protected override void OnCreatePlayerPrefab() { - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); + AddPlayerComponent(); + AddPlayerComponent(); + AddPlayerComponent(); + AddPlayerComponent(); + AddPlayerComponent(); + AddPlayerComponent(); + AddPlayerComponent(); base.OnCreatePlayerPrefab(); } @@ -90,6 +97,7 @@ public IEnumerator TestListBuiltInTypeCollections() { /////////////////////////////////////////////////////////////////////////// // List Single dimension list + compInt = client.LocalClient.PlayerObject.GetComponent(); compIntServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compInt.ValidateInstances()); @@ -99,16 +107,34 @@ public IEnumerator TestListBuiltInTypeCollections() AssertOnTimeout($"[Server] Not all instances of client-{compIntServer.OwnerClientId}'s {nameof(ListTestHelperInt)} {compIntServer.name} component match!"); var randomInt = Random.Range(int.MinValue, int.MaxValue); + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + ////////////////////////////////// + // No Write Owner Add Int + compIntServer.Add(randomInt, ListTestHelperBase.Targets.Owner); + } + ////////////////////////////////// // Owner Add int compInt.Add(randomInt, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); + + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + ////////////////////////////////// + // No Write Server Add Int + compInt.Add(randomInt, ListTestHelperBase.Targets.Server); + } + ////////////////////////////////// // Server Add int compIntServer.Add(randomInt, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); + ////////////////////////////////// // Owner Remove int var index = Random.Range(0, compInt.ListCollectionOwner.Value.Count - 1); @@ -131,12 +157,39 @@ public IEnumerator TestListBuiltInTypeCollections() //////////////////////////////////// // Owner Change int var valueIntChange = Random.Range(int.MinValue, int.MaxValue); + + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // No Write Server Change int with IsDirty restore + compIntServer.ListCollectionOwner.Value[index] = valueIntChange; + compIntServer.ListCollectionOwner.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Server change failed to restore on {nameof(ListTestHelperInt)} {compInt.name}!"); + + // No Write Server Change int with owner state update override + compIntServer.ListCollectionOwner.Value[index] = valueIntChange; + } compInt.ListCollectionOwner.Value[index] = valueIntChange; compInt.ListCollectionOwner.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); + ////////////////////////////////// // Server Change int + + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // No Write Client Change int with IsDirty restore + compInt.ListCollectionServer.Value[index] = valueIntChange; + compInt.ListCollectionServer.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Client-{client.LocalClientId} change failed to restore on {nameof(ListTestHelperInt)} {compInt.name}!"); + + // No Write Client Change int with owner state update override + compInt.ListCollectionServer.Value[index] = valueIntChange; + } compIntServer.ListCollectionServer.Value[index] = valueIntChange; compIntServer.ListCollectionServer.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); @@ -211,13 +264,36 @@ public IEnumerator TestListBuiltInTypeCollections() ////////////////////////////////// // Owner Remove List item index = Random.Range(0, compListInt.ListCollectionOwner.Value.Count - 1); + + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + compListIntServer.ListCollectionOwner.Value.Remove(compListIntServer.ListCollectionOwner.Value[index]); + compListIntServer.ListCollectionOwner.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Server remove failed to restore on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); + // No Write Server Remove List item with update restore + compListIntServer.ListCollectionOwner.Value.Remove(compListIntServer.ListCollectionOwner.Value[index]); + } compListInt.Remove(compListInt.ListCollectionOwner.Value[index], ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {nameof(ListTestHelperListInt)} {compListInt.name}! {compListInt.GetLog()}"); + ////////////////////////////////// // Server Remove List item index = Random.Range(0, compListIntServer.ListCollectionServer.Value.Count - 1); - compListIntServer.Remove(compListIntServer.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Owner); + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // No Write Client Remove List item with CheckDirtyState restore + compListInt.Remove(compListInt.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Server); + yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Client-{client.LocalClientId} remove failed to restore on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); + + // No Write Client Remove List item with update restore + compListInt.Remove(compListInt.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Server); + } + compListIntServer.Remove(compListIntServer.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); @@ -370,12 +446,37 @@ public IEnumerator TestListSerializableObjectCollections() //////////////////////////////////// // Owner Change SerializableObject + + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // No Write Server Remove Serializable item with IsDirty restore + compObjectServer.ListCollectionOwner.Value[index] = SerializableObject.GetRandomObject(); + compObjectServer.ListCollectionOwner.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Server change failed to restore on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); + + // No Write Server Remove Serializable item with owner state update restore + compObjectServer.ListCollectionOwner.Value[index] = SerializableObject.GetRandomObject(); + } compObject.ListCollectionOwner.Value[index] = SerializableObject.GetRandomObject(); compObject.ListCollectionOwner.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {nameof(ListTestHelperSerializableObject)} {compObject.name}!"); ////////////////////////////////// // Server Change SerializableObject + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // No Write Client Remove Serializable item with IsDirty restore + compObject.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); + compObject.ListCollectionServer.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Client-{client.LocalClientId} change failed to restore on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); + + // No Write Client Remove Serializable item with owner state update restore + compObject.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); + } compObjectServer.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); compObjectServer.ListCollectionServer.CheckDirtyState(); yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); @@ -427,7 +528,7 @@ public IEnumerator TestListSerializableObjectCollections() AssertOnTimeout($"[Server] Not all instances of client-{compObjectServer.OwnerClientId}'s {nameof(ListTestHelperSerializableObject)} {compObjectServer.name} component match!"); /////////////////////////////////////////////////////////////////////////// - // List> Nested List Validation + // List> Nested List Validation compListObject = client.LocalClient.PlayerObject.GetComponent(); compListObjectServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compListObject.ValidateInstances()); @@ -437,24 +538,24 @@ public IEnumerator TestListSerializableObjectCollections() AssertOnTimeout($"[Server] Not all instances of client-{compListObjectServer.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name} component match! {compListObjectServer.GetLog()}"); ////////////////////////////////// - // Owner Add List item + // Owner Add List item compListObject.Add(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// - // Server Add List item + // Server Add List item compListObjectServer.Add(SerializableObject.GetListOfRandomObjects(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}! {compListObjectServer.GetLog()}"); ////////////////////////////////// - // Owner Remove List item + // Owner Remove List item index = Random.Range(0, compListObject.ListCollectionOwner.Value.Count - 1); compListObject.Remove(compListObject.ListCollectionOwner.Value[index], ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// - // Server Remove List item + // Server Remove List item index = Random.Range(0, compListObjectServer.ListCollectionServer.Value.Count - 1); compListObjectServer.Remove(compListObjectServer.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); @@ -468,7 +569,7 @@ public IEnumerator TestListSerializableObjectCollections() AssertOnTimeout($"[Server] Not all instances of client-{compListObjectServer.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name} component match! {compListObjectServer.GetLog()}"); //////////////////////////////////// - // Owner Change List item + // Owner Change List item index = Random.Range(0, compListObject.ListCollectionOwner.Value.Count - 1); compListObject.ListCollectionOwner.Value[index] = SerializableObject.GetListOfRandomObjects(5); compListObject.ListCollectionOwner.CheckDirtyState(); @@ -477,7 +578,7 @@ public IEnumerator TestListSerializableObjectCollections() AssertOnTimeout($"Client-{client.LocalClientId} change index ({index}) failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// - // Server Change List item + // Server Change List item index = Random.Range(0, compListObjectServer.ListCollectionServer.Value.Count - 1); compListObjectServer.ListCollectionServer.Value[index] = SerializableObject.GetListOfRandomObjects(5); compListObjectServer.ListCollectionServer.CheckDirtyState(); @@ -486,12 +587,12 @@ public IEnumerator TestListSerializableObjectCollections() AssertOnTimeout($"Server change failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}! {compListObjectServer.GetLog()}"); //////////////////////////////////// - // Owner Add Range of List items + // Owner Add Range of List items compListObject.AddRange(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add range failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}! {compListObject.GetLog()}"); ////////////////////////////////// - // Server Add Range of List items + // Server Add Range of List items compListObjectServer.AddRange(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add range failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}! {compListObjectServer.GetLog()}"); @@ -503,23 +604,46 @@ public IEnumerator TestListSerializableObjectCollections() AssertOnTimeout($"[Server] Not all instances of client-{compListObjectServer.OwnerClientId}'s {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name} component match!"); //////////////////////////////////// - // Owner Full Set List> + // Owner Full Set List> compListObject.FullSet(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}!"); ////////////////////////////////// - // Server Full Set List> + // Server Full Set List> compListObjectServer.FullSet(SerializableObject.GetListOfListOfRandomObjects(5, 5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}!"); //////////////////////////////////// - // Owner Clear List> + // Owner Clear List> + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // Server Clear List> with IsDirty restore + compListObjectServer.ListCollectionOwner.Value.Clear(); + compListObjectServer.ListCollectionOwner.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Server clear owner collection failed to restore back to last known valid state on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}!"); + // Server Clear List> with update state restore + compListObjectServer.ListCollectionOwner.Value.Clear(); + } compListObject.Clear(ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}!"); ////////////////////////////////// - // Server Clear List> + // Server Clear List> + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // Client Clear List> with IsDirty restore + compListObject.ListCollectionServer.Value.Clear(); + compListObject.ListCollectionServer.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compListObject.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Client clear owner collection failed to restore back to last known valid state on {nameof(ListTestHelperListSerializableObject)} {compListObject.name}!"); + + // Client Clear List> with update state restore + compListObject.ListCollectionServer.Value.Clear(); + } compListObjectServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {nameof(ListTestHelperListSerializableObject)} {compListObjectServer.name}!"); @@ -539,6 +663,111 @@ private int GetNextKey() return m_CurrentKey; } + private int m_Stage; + + private List m_Clients; + + private bool m_IsInitialized = false; + private StringBuilder m_InitializedStatus = new StringBuilder(); + + private IEnumerator ValidateClients(NetworkManager clientBeingTested, bool initialize = false) + { + VerboseDebug($">>>>>>>>>>>>>>>>>>>>>>>>>[Client-{clientBeingTested.LocalClientId}][{m_Stage}][Validation]<<<<<<<<<<<<<<<<<<<<<<<<< "); + m_Stage++; + var compDictionary = (DictionaryTestHelper)null; + var compDictionaryServer = (DictionaryTestHelper)null; + var className = $"{nameof(DictionaryTestHelper)}"; + var clientsInitialized = new Dictionary(); + + var validateTimeout = new TimeoutHelper(0.25f); + + foreach (var client in m_Clients) + { + var ownerInitialized = false; + var serverInitialized = false; + /////////////////////////////////////////////////////////////////////////// + // Dictionary> nested dictionaries + compDictionary = client.LocalClient.PlayerObject.GetComponent(); + compDictionaryServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); + yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances(), validateTimeout); + if (initialize) + { + if (validateTimeout.HasTimedOut()) + { + m_InitializedStatus.AppendLine($"[Client -{client.LocalClientId}][Owner] Failed validation: {compDictionary.GetLog()}"); + } + else + { + m_InitializedStatus.AppendLine($"[Client -{client.LocalClientId}][Owner] Passed validation!"); + } + ownerInitialized = !validateTimeout.HasTimedOut(); + } + else + { + AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); + } + + yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances(), validateTimeout); + if (initialize) + { + if (validateTimeout.HasTimedOut()) + { + m_InitializedStatus.AppendLine($"[Client -{client.LocalClientId}][Server] Failed validation: {compDictionaryServer.GetLog()}"); + } + else + { + m_InitializedStatus.AppendLine($"[Client -{client.LocalClientId}][Server] Passed validation!"); + } + serverInitialized = !validateTimeout.HasTimedOut(); + } + else + { + AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); + } + + if (initialize) + { + clientsInitialized.Add(client.LocalClientId, ownerInitialized & serverInitialized); + } + } + + if (initialize) + { + m_IsInitialized = true; + foreach (var entry in clientsInitialized) + { + if (!entry.Value) + { + m_IsInitialized = false; + break; + } + } + } + } + + private void ValidateClientsFlat(NetworkManager clientBeingTested) + { + if (!m_EnableDebug) + { + return; + } + VerboseDebug($">>>>>>>>>>>>>>>>>>>>>>>>>[{clientBeingTested.name}][{m_Stage}][Validation]<<<<<<<<<<<<<<<<<<<<<<<<< "); + m_Stage++; + var compDictionary = (DictionaryTestHelper)null; + var compDictionaryServer = (DictionaryTestHelper)null; + var className = $"{nameof(DictionaryTestHelper)}"; + foreach (var client in m_Clients) + { + /////////////////////////////////////////////////////////////////////////// + // Dictionary> nested dictionaries + compDictionary = client.LocalClient.PlayerObject.GetComponent(); + compDictionaryServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); + Assert.True(compDictionary.ValidateInstances(), $"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); + Assert.True(compDictionaryServer.ValidateInstances(), $"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); + } + } + + [UnityTest] public IEnumerator TestDictionaryCollections() { @@ -546,15 +775,47 @@ public IEnumerator TestDictionaryCollections() var compDictionaryServer = (DictionaryTestHelper)null; var className = $"{nameof(DictionaryTestHelper)}"; - var clientList = m_ClientNetworkManagers.ToList(); + m_Clients = m_ClientNetworkManagers.ToList(); if (m_ServerNetworkManager.IsHost) { - clientList.Insert(0, m_ServerNetworkManager); + m_Clients.Insert(0, m_ServerNetworkManager); } m_CurrentKey = 1000; - foreach (var client in clientList) + if (m_EnableDebug) + { + VerboseDebug(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> Init Values <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + foreach (var client in m_Clients) + { + compDictionary = client.LocalClient.PlayerObject.GetComponent(); + compDictionary.InitValues(); + compDictionaryServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); + compDictionaryServer.InitValues(); + } + VerboseDebug(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> Init Check <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + var count = 0; + while (count < 3) + { + m_InitializedStatus.Clear(); + foreach (var client in m_Clients) + { + yield return ValidateClients(client, true); + } + if (m_IsInitialized) + { + break; + } + count++; + m_Stage = 0; + } + + Assert.IsTrue(m_IsInitialized, $"Not all clients synchronized properly!\n {m_InitializedStatus.ToString()}"); + VerboseDebug(m_InitializedStatus.ToString()); + } + + VerboseDebug(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + foreach (var client in m_Clients) { /////////////////////////////////////////////////////////////////////////// // Dictionary> nested dictionaries @@ -562,18 +823,55 @@ public IEnumerator TestDictionaryCollections() compDictionaryServer = m_PlayerNetworkObjects[NetworkManager.ServerClientId][client.LocalClientId].GetComponent(); yield return WaitForConditionOrTimeOut(() => compDictionary.ValidateInstances()); AssertOnTimeout($"[Owner] Not all instances of client-{compDictionary.OwnerClientId}'s {className} {compDictionary.name} component match! {compDictionary.GetLog()}"); - yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); ////////////////////////////////// // Owner Add SerializableObject Entry - compDictionary.Add((GetNextKey(), SerializableObject.GetRandomObject()), ListTestHelperBase.Targets.Owner); + var newEntry = (GetNextKey(), SerializableObject.GetRandomObject()); + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // Server-side add same key and SerializableObject prior to being added to the owner side + compDictionaryServer.ListCollectionOwner.Value.Add(newEntry.Item1, newEntry.Item2); + // Checking if dirty on server side should revert back to origina known current dictionary state + compDictionaryServer.ListCollectionOwner.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Server add to owner write collection property failed to restore on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); + // Server-side add the same key and SerializableObject to owner write permission (would throw key exists exception too if previous failed) + compDictionaryServer.ListCollectionOwner.Value.Add(newEntry.Item1, newEntry.Item2); + // Server-side add a completely new key and SerializableObject to to owner write permission property + compDictionaryServer.ListCollectionOwner.Value.Add(GetNextKey(), SerializableObject.GetRandomObject()); + // Both should be overridden by the owner-side update + + } + VerboseDebug($"[{compDictionary.name}][Owner] Adding Key: {newEntry.Item1}"); + // Add key and SerializableObject to owner side + compDictionary.Add(newEntry, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} add failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); + + ValidateClientsFlat(client); ////////////////////////////////// // Server Add SerializableObject Entry - compDictionaryServer.Add((GetNextKey(), SerializableObject.GetRandomObject()), ListTestHelperBase.Targets.Server); + newEntry = (GetNextKey(), SerializableObject.GetRandomObject()); + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // Client-side add same key and SerializableObject to server write permission property + compDictionary.ListCollectionServer.Value.Add(newEntry.Item1, newEntry.Item2); + // Checking if dirty on client side should revert back to origina known current dictionary state + compDictionary.ListCollectionServer.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Client-{client.LocalClientId} add to server write collection property failed to restore on {className} {compDictionary.name}! {compDictionary.GetLog()}"); + // Client-side add the same key and SerializableObject to server write permission property (would throw key exists exception too if previous failed) + compDictionary.ListCollectionServer.Value.Add(newEntry.Item1, newEntry.Item2); + // Client-side add a completely new key and SerializableObject to to server write permission property + compDictionary.ListCollectionServer.Value.Add(GetNextKey(), SerializableObject.GetRandomObject()); + // Both should be overridden by the server-side update + } + VerboseDebug($"[{compDictionaryServer.name}][Server] Adding Key: {newEntry.Item1}"); + compDictionaryServer.Add(newEntry, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server add failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); ////////////////////////////////// @@ -583,10 +881,11 @@ public IEnumerator TestDictionaryCollections() compDictionary.Remove(valueInt, ListTestHelperBase.Targets.Owner); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); + ////////////////////////////////// // Server Remove SerializableObject Entry - index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); - valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; + index = Random.Range(0, compDictionary.ListCollectionServer.Value.Keys.Count - 1); + valueInt = compDictionary.ListCollectionServer.Value.Keys.ToList()[index]; compDictionaryServer.Remove(valueInt, ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server remove failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); @@ -597,41 +896,101 @@ public IEnumerator TestDictionaryCollections() yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); + ValidateClientsFlat(client); //////////////////////////////////// // Owner Change SerializableObject Entry - index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); - valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; - compDictionary.ListCollectionOwner.Value[valueInt] = SerializableObject.GetRandomObject(); - compDictionary.ListCollectionOwner.CheckDirtyState(); - yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); - AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); + var randomObject = SerializableObject.GetRandomObject(); + if (compDictionary.ListCollectionOwner.Value.Keys.Count != 0) + { + if (compDictionary.ListCollectionOwner.Value.Keys.Count == 1) + { + index = 0; + valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[0]; + } + else + { + index = Random.Range(0, compDictionary.ListCollectionOwner.Value.Keys.Count - 1); + valueInt = compDictionary.ListCollectionOwner.Value.Keys.ToList()[index]; + } + + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // Server-side update same key value prior to being updated to the owner side + compDictionaryServer.ListCollectionOwner.Value[valueInt] = randomObject; + // Checking if dirty on server side should revert back to origina known current dictionary state + compDictionaryServer.ListCollectionOwner.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Server update collection entry value to local owner write collection property failed to restore on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); + + // Server-side update same key but with different value prior to being updated to the owner side + compDictionaryServer.ListCollectionOwner.Value[valueInt] = SerializableObject.GetRandomObject(); + if (compDictionaryServer.ListCollectionOwner.Value.Keys.Count > 1) + { + // Server-side update different key with different value prior to being updated to the owner side + compDictionaryServer.ListCollectionOwner.Value[compDictionaryServer.ListCollectionOwner.Value.Keys.ToList()[(index + 1) % compDictionaryServer.ListCollectionOwner.Value.Keys.Count]] = SerializableObject.GetRandomObject(); + } + // Owner-side update should force restore to current known value before updating to the owner's state update of the original index and SerializableObject + } + + compDictionary.ListCollectionOwner.Value[valueInt] = randomObject; + compDictionary.ListCollectionOwner.CheckDirtyState(); + yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Client-{client.LocalClientId} change failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); + } + ////////////////////////////////// // Server Change SerializableObject - index = Random.Range(0, compDictionaryServer.ListCollectionOwner.Value.Keys.Count - 1); - valueInt = compDictionaryServer.ListCollectionOwner.Value.Keys.ToList()[index]; - compDictionaryServer.ListCollectionServer.Value[valueInt] = SerializableObject.GetRandomObject(); - compDictionaryServer.ListCollectionServer.CheckDirtyState(); - yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); - AssertOnTimeout($"Server change failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); + if (compDictionaryServer.ListCollectionServer.Value.Keys.Count != 0) + { + if (compDictionaryServer.ListCollectionServer.Value.Keys.Count == 1) + { + index = 0; + valueInt = compDictionaryServer.ListCollectionServer.Value.Keys.ToList()[0]; + } + else + { + index = Random.Range(0, compDictionaryServer.ListCollectionServer.Value.Keys.Count - 1); + valueInt = compDictionaryServer.ListCollectionServer.Value.Keys.ToList()[index]; + } - //////////////////////////////////// - // Owner Full Set Dictionary - compDictionary.FullSet(DictionaryTestHelper.GetDictionaryValues(), ListTestHelperBase.Targets.Owner); - yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); - AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); - ////////////////////////////////// - // Server Full Set Dictionary - compDictionaryServer.FullSet(DictionaryTestHelper.GetDictionaryValues(), ListTestHelperBase.Targets.Server); - yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); - AssertOnTimeout($"Server full set failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); + // Only test restore on non-host clients (otherwise a host is both server and client/owner) + if (!client.IsServer) + { + // Owner-side update same key value prior to being updated to the server side + compDictionary.ListCollectionServer.Value[valueInt] = randomObject; + // Checking if dirty on owner side should revert back to origina known current dictionary state + compDictionary.ListCollectionServer.IsDirty(); + yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Client-{client.LocalClientId} update collection entry value to local server write collection property failed to restore on {className} {compDictionary.name}! {compDictionary.GetLog()}"); + + // Owner-side update same key but with different value prior to being updated to the server side + compDictionary.ListCollectionServer.Value[valueInt] = SerializableObject.GetRandomObject(); + + if (compDictionary.ListCollectionServer.Value.Keys.Count > 1) + { + // Owner-side update different key with different value prior to being updated to the server side + compDictionary.ListCollectionServer.Value[compDictionary.ListCollectionServer.Value.Keys.ToList()[(index + 1) % compDictionary.ListCollectionServer.Value.Keys.Count]] = SerializableObject.GetRandomObject(); + } + // Server-side update should force restore to current known value before updating to the server's state update of the original index and SerializableObject + } + + compDictionaryServer.ListCollectionServer.Value[valueInt] = SerializableObject.GetRandomObject(); + compDictionaryServer.ListCollectionServer.CheckDirtyState(); + yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Server change failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); + } + ValidateClientsFlat(client); //////////////////////////////////// // Owner Clear compDictionary.Clear(ListTestHelperBase.Targets.Owner); + VerboseDebug($"[{compDictionary.name}] Clearing dictionary.."); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} clear failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); ////////////////////////////////// // Server Clear + VerboseDebug($"[{compDictionaryServer.name}] Clearing dictionary.."); compDictionaryServer.Clear(ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server clear failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); @@ -641,6 +1000,22 @@ public IEnumerator TestDictionaryCollections() yield return WaitForConditionOrTimeOut(() => compDictionaryServer.ValidateInstances()); AssertOnTimeout($"[Server] Not all instances of client-{compDictionaryServer.OwnerClientId}'s {className} {compDictionaryServer.name} component match! {compDictionaryServer.GetLog()}"); + + //////////////////////////////////// + // Owner Full Set Dictionary + compDictionary.FullSet(DictionaryTestHelper.GetDictionaryValues(), ListTestHelperBase.Targets.Owner); + yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); + AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {className} {compDictionary.name}! {compDictionary.GetLog()}"); + ////////////////////////////////// + // Server Full Set Dictionary + compDictionaryServer.FullSet(DictionaryTestHelper.GetDictionaryValues(), ListTestHelperBase.Targets.Server); + yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); + AssertOnTimeout($"Server full set failed to synchronize on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); + if (m_EnableDebug) + { + yield return ValidateClients(client); + m_Stage = 0; + } } } @@ -837,6 +1212,505 @@ public IEnumerator TestHashSetBuiltInTypeCollections() } } + [TestFixture(HostOrServer.DAHost, CollectionTypes.List)] + [TestFixture(HostOrServer.DAHost, CollectionTypes.Dictionary)] + [TestFixture(HostOrServer.Host, CollectionTypes.List)] + [TestFixture(HostOrServer.Host, CollectionTypes.Dictionary)] + [TestFixture(HostOrServer.Server, CollectionTypes.List)] + [TestFixture(HostOrServer.Server, CollectionTypes.Dictionary)] + public class NetworkVariableCollectionsChangingTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + public enum CollectionTypes + { + Dictionary, + List, + } + private StringBuilder m_ErrorLog = new StringBuilder(); + private CollectionTypes m_CollectionType; + private GameObject m_TestPrefab; + private NetworkObject m_Instance; + + public NetworkVariableCollectionsChangingTests(HostOrServer hostOrServer, CollectionTypes collectionType) : base(hostOrServer) + { + m_CollectionType = collectionType; + } + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("TestObject"); + if (m_CollectionType == CollectionTypes.Dictionary) + { + m_TestPrefab.AddComponent(); + } + else + { + m_TestPrefab.AddComponent(); + } + if (m_DistributedAuthority) + { + var networkObject = m_TestPrefab.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + } + base.OnServerAndClientsCreated(); + } + + private bool AllInstancesSpawned() + { + if (!UseCMBService()) + { + if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_Instance.NetworkObjectId)) + { + return false; + } + } + + foreach (var client in m_ClientNetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(m_Instance.NetworkObjectId)) + { + return false; + } + } + return true; + } + + private Dictionary m_NetworkManagers = new Dictionary(); + + private bool ValidateAllInstances() + { + if (!m_NetworkManagers.ContainsKey(m_Instance.OwnerClientId)) + { + return false; + } + + if (!m_NetworkManagers[m_Instance.OwnerClientId].SpawnManager.SpawnedObjects.ContainsKey(m_Instance.NetworkObjectId)) + { + return false; + } + + var ownerNetworkManager = m_NetworkManagers[m_Instance.OwnerClientId]; + + var ownerClientInstance = m_NetworkManagers[m_Instance.OwnerClientId].SpawnManager.SpawnedObjects[m_Instance.NetworkObjectId].GetComponent(); + + foreach (var client in m_NetworkManagers) + { + if (client.Value == ownerNetworkManager) + { + continue; + } + + var otherInstance = client.Value.SpawnManager.SpawnedObjects[m_Instance.NetworkObjectId].GetComponent(); + if (!ownerClientInstance.ValidateAgainst(otherInstance)) + { + return false; + } + } + return true; + } + + private bool OwnershipChangedOnAllClients(ulong expectedOwner) + { + m_ErrorLog.Clear(); + foreach (var client in m_NetworkManagers) + { + var otherInstance = client.Value.SpawnManager.SpawnedObjects[m_Instance.NetworkObjectId].GetComponent(); + if (otherInstance.OwnerClientId != expectedOwner) + { + m_ErrorLog.AppendLine($"Client-{client.Value.LocalClientId} instance of {m_Instance.name} still shows the owner is Client-{otherInstance.OwnerClientId} when it should be Client-{expectedOwner}!"); + return false; + } + } + return true; + } + + private BaseCollectionUpdateHelper GetOwnerInstance() + { + var ownerNetworkManager = m_NetworkManagers[m_Instance.OwnerClientId]; + return m_NetworkManagers[m_Instance.OwnerClientId].SpawnManager.SpawnedObjects[m_Instance.NetworkObjectId].GetComponent(); + } + + /// + /// Gets the authority instance. + /// Client-Server: will always return the server-side instance + /// Distributed Authority: will always return the owner + /// + /// authority instance + private BaseCollectionUpdateHelper GetAuthorityInstance() + { + if (m_DistributedAuthority) + { + return GetOwnerInstance(); + } + else + { + return m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_Instance.NetworkObjectId].GetComponent(); + } + } + + [UnityTest] + public IEnumerator CollectionAndOwnershipChangingTest() + { + BaseCollectionUpdateHelper.VerboseMode = m_EnableVerboseDebug; + var runWaitPeriod = new WaitForSeconds(0.5f); + m_NetworkManagers.Clear(); + if (!UseCMBService() && m_UseHost) + { + m_NetworkManagers.Add(m_ServerNetworkManager.LocalClientId, m_ServerNetworkManager); + } + foreach (var client in m_ClientNetworkManagers) + { + m_NetworkManagers.Add(client.LocalClientId, client); + } + + var authorityNetworkManager = UseCMBService() || !m_UseHost ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; + + var instance = SpawnObject(m_TestPrefab, authorityNetworkManager); + m_Instance = instance.GetComponent(); + var helper = instance.GetComponent(); + var currentOwner = helper.OwnerClientId; + yield return WaitForConditionOrTimeOut(AllInstancesSpawned); + AssertOnTimeout($"[Pre][1st Phase] Timed out waiting for all clients to spawn {m_Instance.name}!"); + helper.SetState(BaseCollectionUpdateHelper.HelperStates.Start); + yield return runWaitPeriod; + + // Update values, validate values, change owner, updates values, and repeat until all clients have been the owner at least once + for (int i = 0; i < 4; i++) + { + helper.SetState(BaseCollectionUpdateHelper.HelperStates.Pause); + yield return WaitForConditionOrTimeOut(ValidateAllInstances); + AssertOnTimeout($"[1st Phase] Timed out waiting for all clients to validdate their values!"); + helper.SetState(BaseCollectionUpdateHelper.HelperStates.Start); + yield return s_DefaultWaitForTick; + + currentOwner = GetAuthorityInstance().ChangeOwner(); + Assert.IsFalse(currentOwner == ulong.MaxValue, "A non-authority instance attempted to change ownership!"); + + yield return WaitForConditionOrTimeOut(() => OwnershipChangedOnAllClients(currentOwner)); + AssertOnTimeout($"[1st Phase] Timed out waiting for all clients to change ownership!\n {m_ErrorLog.ToString()}"); + helper = GetOwnerInstance(); + yield return runWaitPeriod; + } + + // Now reset the values + helper.SetState(BaseCollectionUpdateHelper.HelperStates.Pause); + helper.Clear(); + + // Validate all instances are reset + yield return WaitForConditionOrTimeOut(ValidateAllInstances); + AssertOnTimeout($"[Pre][2nd Phase]Timed out waiting for all clients to validdate their values!"); + helper.SetState(BaseCollectionUpdateHelper.HelperStates.Start); + + // Update, change ownership, and repeat until all clients have been the owner at least once + for (int i = 0; i < 4; i++) + { + yield return runWaitPeriod; + currentOwner = GetAuthorityInstance().ChangeOwner(); + Assert.IsFalse(currentOwner == ulong.MaxValue, "A non-authority instance attempted to change ownership!"); + yield return WaitForConditionOrTimeOut(() => OwnershipChangedOnAllClients(currentOwner)); + AssertOnTimeout($"[2nd Phase] Timed out waiting for all clients to change ownership!"); + helper = GetOwnerInstance(); + } + + helper.SetState(BaseCollectionUpdateHelper.HelperStates.Pause); + yield return WaitForConditionOrTimeOut(ValidateAllInstances); + AssertOnTimeout($"[Last Validate] Timed out waiting for all clients to validdate their values!"); + } + } + + #region COLLECTION CHANGING COMPONENTS + /// + /// Helper class to test adding dictionary entries rapidly with frequent ownership changes. + /// This includes a companion integer that is continually incremented and used as the key value for each entry. + /// + public class DictionaryCollectionUpdateHelper : BaseCollectionUpdateHelper + { + private NetworkVariable> m_DictionaryCollection = new NetworkVariable>(new Dictionary(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + private NetworkVariable m_CurrentKeyValue = new NetworkVariable(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + protected override bool OnValidateAgainst(BaseCollectionUpdateHelper otherHelper) + { + var otherListHelper = otherHelper as DictionaryCollectionUpdateHelper; + var localValues = m_DictionaryCollection.Value; + var otherValues = otherListHelper.m_DictionaryCollection.Value; + + if (localValues.Count != otherValues.Count) + { + return false; + } + + foreach (var entry in m_DictionaryCollection.Value) + { + if (!otherValues.ContainsKey(entry.Key)) + { + return false; + } + + if (entry.Value != otherValues[entry.Key]) + { + return false; + } + } + return true; + } + protected override void OnClear() + { + m_DictionaryCollection.Value.Clear(); + m_DictionaryCollection.CheckDirtyState(); + base.OnClear(); + } + + protected override void AddItem() + { + m_DictionaryCollection.Value.Add(m_CurrentKeyValue.Value, m_CurrentKeyValue.Value); + m_DictionaryCollection.CheckDirtyState(); + m_CurrentKeyValue.Value++; + } + } + + /// + /// Helper class to test adding list entries rapidly with frequent ownership changes + /// + public class ListCollectionUpdateHelper : BaseCollectionUpdateHelper + { + private NetworkVariable> m_ListCollection = new NetworkVariable>(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + + protected override bool OnValidateAgainst(BaseCollectionUpdateHelper otherHelper) + { + var otherListHelper = otherHelper as ListCollectionUpdateHelper; + var localValues = m_ListCollection.Value; + var otherValues = otherListHelper.m_ListCollection.Value; + + if (localValues.Count != otherValues.Count) + { + return false; + } + + for (int i = 0; i < localValues.Count - 1; i++) + { + if (localValues[i] != i) + { + return false; + } + + if (localValues[i] != otherValues[i]) + { + return false; + } + } + return true; + } + + protected override void OnClear() + { + m_ListCollection.Value.Clear(); + m_ListCollection.CheckDirtyState(); + base.OnClear(); + } + + protected override void AddItem() + { + m_ListCollection.Value.Add(m_ListCollection.Value.Count); + m_ListCollection.CheckDirtyState(); + } + } + + /// + /// The base class to test rapidly adding items to a collection type + /// + public class BaseCollectionUpdateHelper : NetworkBehaviour + { + public static bool VerboseMode; + private const int k_OwnershipTickDelay = 1; + + public enum HelperStates + { + Stop, + Start, + Pause, + ClearToChangeOwner, + ChangingOwner + } + public HelperStates HelperState { get; private set; } + + private int m_SendClearForOwnershipOnTick; + private ulong m_NextClient = 0; + private ulong m_ClientToSendClear = 0; + + public void SetState(HelperStates helperState) + { + HelperState = helperState; + } + + protected virtual bool OnValidateAgainst(BaseCollectionUpdateHelper otherHelper) + { + return true; + } + + public bool ValidateAgainst(BaseCollectionUpdateHelper otherHelper) + { + return OnValidateAgainst(otherHelper); + } + + public override void OnNetworkSpawn() + { + // Register for tick updates + NetworkManager.NetworkTickSystem.Tick += OnNetworkTick; + + base.OnNetworkSpawn(); + } + public override void OnNetworkDespawn() + { + NetworkManager.NetworkTickSystem.Tick -= OnNetworkTick; + base.OnNetworkDespawn(); + } + + protected virtual void OnClear() + { + } + + public void Clear() + { + OnClear(); + } + + protected virtual void AddItem() + { + } + + private bool CanUpdate() + { + return HelperState == HelperStates.Start; + } + + private void Update() + { + // Exit early if not spawn, updating is not enabled, or is not the owner + if (!IsSpawned || !CanUpdate() || !IsOwner) + { + return; + } + + AddItem(); + } + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + // When the ownership changes and the client is the owner, then immediately add an item to the collection + if (NetworkManager.LocalClientId == current) + { + AddItem(); + } + base.OnOwnershipChanged(previous, current); + } + + + /// + /// Sets the tick delay period of time to provide all in-flight deltas to be processed. + /// + private void SetTickDelay() + { + m_SendClearForOwnershipOnTick = NetworkManager.ServerTime.Tick + k_OwnershipTickDelay; + } + + /// + /// Changes the ownership + /// + /// next owner or ulong.MaxValue that means the authority did not invoke this method + public ulong ChangeOwner() + { + if (HasAuthority && !IsOwnershipChanging()) + { + var index = NetworkManager.ConnectedClientsIds.ToList().IndexOf(OwnerClientId); + index++; + index = index % NetworkManager.ConnectedClientsIds.Count; + m_NextClient = NetworkManager.ConnectedClientsIds[index]; + + // If we are in distributed authority and the authority or we are in client-server and the server, then make the change ourselves. + if (OwnerClientId == NetworkManager.LocalClientId && (NetworkManager.DistributedAuthorityMode || (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer))) + { + HelperState = HelperStates.ChangingOwner; + SetTickDelay(); + Log($"Locally changing ownership to Client-{m_NextClient}"); + } + + if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && OwnerClientId != NetworkManager.LocalClientId) + { + // If we are transitioning between a client to the host or client to client, + // send a "heads-up" Rpc to the client prior to changing ownership. The client + // will stop updating for the tick delay period and then send a confirmation + // to the host that it is clear to change ownership. + ChangingOwnershipRpc(RpcTarget.Single(OwnerClientId, RpcTargetUse.Temp)); + Log($"Remotely changing ownership to Client-{m_NextClient}"); + } + + return m_NextClient; + } + + return ulong.MaxValue; + } + + /// + /// Sent by the host to a client when ownership is transitioning from a client to + /// the host or to another client. + /// + [Rpc(SendTo.SpecifiedInParams)] + private void ChangingOwnershipRpc(RpcParams rpcParams = default) + { + // The sender is who we respond to that it is clear to change ownership + m_ClientToSendClear = rpcParams.Receive.SenderClientId; + HelperState = HelperStates.ClearToChangeOwner; + SetTickDelay(); + } + + /// + /// Notification that the current owner has stopped updating and ownership + /// updates can occur without missed updates. + /// + /// + [Rpc(SendTo.SpecifiedInParams)] + private void ChangingOwnershipClearRpc(RpcParams rpcParams = default) + { + HelperState = HelperStates.ChangingOwner; + SetTickDelay(); + Log($"Changing ownership to Client-{m_NextClient} based on ready request."); + } + + private bool IsOwnershipChanging() + { + return HelperState == HelperStates.ClearToChangeOwner || HelperState == HelperStates.ChangingOwner; + } + + private void OnNetworkTick() + { + if (!IsSpawned || !IsOwnershipChanging() || m_SendClearForOwnershipOnTick > NetworkManager.ServerTime.Tick) + { + return; + } + + if (HelperState == HelperStates.ChangingOwner) + { + NetworkObject.ChangeOwnership(m_NextClient); + Log($"Local Change ownership to Client-{m_NextClient} complete! New Owner is {NetworkObject.OwnerClientId} | Expected {m_NextClient}"); + } + else + { + ChangingOwnershipClearRpc(RpcTarget.Single(m_ClientToSendClear, RpcTargetUse.Temp)); + } + HelperState = HelperStates.Stop; + } + + protected void Log(string msg) + { + if (VerboseMode) + { + Debug.Log($"[Client-{NetworkManager.LocalClientId}] {msg}"); + } + } + } + #endregion + #region HASHSET COMPONENT HELPERS public class HashSetBaseTypeTestHelper : ListTestHelperBase, IHashSetTestHelperBase { @@ -1649,6 +2523,14 @@ protected override void OnNetworkPostSpawn() ListCollectionServer.OnValueChanged += OnServerListValuesChanged; ListCollectionOwner.OnValueChanged += OnOwnerListValuesChanged; + if (!IsDebugMode) + { + InitValues(); + } + } + + public void InitValues() + { if (IsServer) { ListCollectionServer.Value = OnSetServerValues(); @@ -1660,8 +2542,8 @@ protected override void OnNetworkPostSpawn() ListCollectionOwner.Value = OnSetOwnerValues(); ListCollectionOwner.CheckDirtyState(); } - base.OnNetworkPostSpawn(); } + public override void OnNetworkDespawn() { ListCollectionServer.OnValueChanged -= OnServerListValuesChanged; @@ -1705,12 +2587,15 @@ public static List> GetListOfListOfRandomObjects(int nu return list; } - - public int IntValue; public long LongValue; public float FloatValue; + public override string ToString() + { + return $"{IntValue},{LongValue},{FloatValue}"; + } + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref IntValue); @@ -2602,7 +3487,6 @@ public static void ResetState() Instances.Clear(); } - public NetworkVariable> ListCollectionServer = new NetworkVariable>(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public NetworkVariable> ListCollectionOwner = new NetworkVariable>(new List(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // This tracks what has changed per instance which is used to compare to all other instances @@ -2865,6 +3749,8 @@ public override void OnNetworkDespawn() #region BASE TEST COMPONENT HELPERS public class ListTestHelperBase : NetworkBehaviour { + protected static bool IsDebugMode { get; private set; } + public enum Targets { Server, @@ -2897,6 +3783,10 @@ protected void LogStart() m_StringBuilder.AppendLine($"[Client-{NetworkManager.LocalClientId}][{name}] Log Started."); } + public void SetDebugMode(bool isDebug) + { + IsDebugMode = isDebug; + } public virtual bool CompareTrackedChanges(Targets target) { From 950969607e303909e6473699d3cb97f4a3d97be7 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 17 Oct 2024 12:31:09 -0500 Subject: [PATCH 113/236] fix: prefab processor dirty up port (up port) (#3103) * fix NetworkPrefabProcessor now marks the default NetworkPrefabsList ScriptableObject asset as dirty if there are new imports OR deletions. * update adding changelog entry * update adding pr number to entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Editor/Configuration/NetworkPrefabProcessor.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 971c8b5db6..253d27f226 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -19,6 +19,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103) - Fixed issue with nested `NetworkTransform` components clearing their initial prefab settings when in owner authoritative mode on the server side while using a client-server network topology which resulted in improper synchronization of the nested `NetworkTransform` components. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs index 879a8c3e75..55f5fcbfc4 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs @@ -132,7 +132,7 @@ bool ProcessDeletedAssets(string[] strings) // Process the imported and deleted assets var markDirty = ProcessImportedAssets(importedAssets); - markDirty &= ProcessDeletedAssets(deletedAssets); + markDirty |= ProcessDeletedAssets(deletedAssets); if (markDirty) { From c2ab328b22e866d8a5a84acd87b5caa5619b1ab0 Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:53:24 -0400 Subject: [PATCH 114/236] chore: NGO 2.1.0 merge back to develop NGO develop 2.0.0 (#3104) * update changelog to match release version * update version of NGO * bumping ngo version to 2.1.0 --------- Co-authored-by: NoelStephensUnity --- com.unity.netcode.gameobjects/CHANGELOG.md | 8 ++++++++ com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 253d27f226..bd7b7b8793 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,14 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +### Fixed + +### Changed + +## [2.1.0] - 2024-10-17 + +### Added + - Added ability to edit the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` within the inspector view. (#3097) - Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094) - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 1878824aa6..8ad3bffa32 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.0.0", + "version": "2.1.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 264b30d176dd71fcedd022a8d6f4d59a2e3922bc Mon Sep 17 00:00:00 2001 From: Frank Luong Date: Fri, 18 Oct 2024 12:22:33 -0400 Subject: [PATCH 115/236] bump version to 2.1.1 --- com.unity.netcode.gameobjects/CHANGELOG.md | 19 +++++-------------- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index bd7b7b8793..3423d9abd1 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,15 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -[Unreleased] - -### Added - -### Fixed - -### Changed - -## [2.1.0] - 2024-10-17 +## [2.1.1] - 2024-10-18 ### Added @@ -28,12 +20,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103) -- Fixed issue with nested `NetworkTransform` components clearing their initial prefab settings when in owner authoritative mode on the server side while using a client-server network topology which resulted in improper synchronization of the nested `NetworkTransform` components. (#3099) +- Fixed an issue where nested `NetworkTransform` components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) -- Fixed issue where a newly synchronizing client would be synchronized with the current `NetworkVariable` values always which could cause issues with collections if there were any pending state updates. Now, when initially synchronizing a client, if a `NetworkVariable` has a pending state update it will serialize the previously known value(s) to the synchronizing client so when the pending updates are sent they aren't duplicate values on the newly connected client side. (#3081) +- Fixed an issue where newly synchronizing clients would always receive current `NetworkVariable` values, potentially causing issues with collections if there were pending updates. Now, pending state updates serialize previous values to avoid duplicates on new clients. (#3081) - Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3081) -- Fixed issue with `NetworkVariable` collections where transferring ownership to another client would not update the new owner's previous value to the most current value which could cause the last/previous added value to be detected as a change when adding or removing an entry (as long as the entry removed was not the last/previously added value). (#3081) +- Fixed an issue where transferring ownership of `NetworkVariable` collections didn't update the new owner’s previous value, causing the last added value to be detected as a change during additions or removals. (#3081) - Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3081) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) @@ -42,8 +34,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097) -- Changed `NetworkVariableDeltaMessage` so the server now forwards delta state updates (owner write permission based from a client) to other clients immediately as opposed to keeping a `NetworkVariable` or `NetworkList` dirty and processing them at the end of the frame or potentially on the next network tick. (#3081) - +- Updated `NetworkVariableDeltaMessage` so the server now forwards delta state updates from clients immediately, instead of waiting until the end of the frame or the next network tick. (#3081) ## [2.0.0] - 2024-09-12 diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 8ad3bffa32..a632de0172 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.1.0", + "version": "2.1.1", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 5378d4d56b9d2cb08d0f604bc2fc11ff5f5df84a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 18 Oct 2024 15:29:50 -0500 Subject: [PATCH 116/236] fix: ping tool example updates and fixes (#3106) * fix Fixed issue with negative ping time calculations. Fixed issue with potential exception on exiting. Fixed issue with serverhostclient identifier text not displaying in the render view. * update Includes some general updates to the project. Updated to ExtendedNetworkManager that combines the NetworkManager, NetworkManagerHelper, and DisconnectNotify into a single component. Added PingToolHelper label that provides on-screen directions as to which key to press to toggle the ping tool. --- .../Assets/DefaultNetworkPrefabs.asset | 4 +- Examples/PingTool/Assets/PingTool/PingTool.cs | 42 +++- .../PingTool/Assets/PingTool/PingTool.prefab | 101 +++++++- .../PingTool/Assets/Scenes/SampleScene.unity | 83 +++---- ...gerHelper.cs => ExtendedNetworkManager.cs} | 221 ++++++++++++------ .../Scripts/ExtendedNetworkManager.cs.meta | 2 + .../Scripts/NetworkManagerHelper.cs.meta | 11 - .../Assets/Scripts/OnDisconnectNotify.cs | 35 --- .../Assets/Scripts/OnDisconnectNotify.cs.meta | 11 - .../Assets/Scripts/ServerHostClientText.cs | 8 +- Examples/PingTool/Packages/manifest.json | 17 +- 11 files changed, 333 insertions(+), 202 deletions(-) rename Examples/PingTool/Assets/Scripts/{NetworkManagerHelper.cs => ExtendedNetworkManager.cs} (54%) create mode 100644 Examples/PingTool/Assets/Scripts/ExtendedNetworkManager.cs.meta delete mode 100644 Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs.meta delete mode 100644 Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs delete mode 100644 Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs.meta diff --git a/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset b/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset index 073f4484e8..72cef84a6d 100644 --- a/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset +++ b/Examples/PingTool/Assets/DefaultNetworkPrefabs.asset @@ -15,12 +15,12 @@ MonoBehaviour: IsDefault: 1 List: - Override: 0 - Prefab: {fileID: 2522762726852386808, guid: 380c984d34fc8664c8f53fc1d8733a25, type: 3} + Prefab: {fileID: 8921789205124766477, guid: 89b57e576a8d47643b2dbd45b1f8cab1, type: 3} SourcePrefabToOverride: {fileID: 0} SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} - Override: 0 - Prefab: {fileID: 8921789205124766477, guid: 89b57e576a8d47643b2dbd45b1f8cab1, type: 3} + Prefab: {fileID: 4874009706086500699, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} SourcePrefabToOverride: {fileID: 0} SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} diff --git a/Examples/PingTool/Assets/PingTool/PingTool.cs b/Examples/PingTool/Assets/PingTool/PingTool.cs index e27887d503..32b0da0fa8 100644 --- a/Examples/PingTool/Assets/PingTool/PingTool.cs +++ b/Examples/PingTool/Assets/PingTool/PingTool.cs @@ -28,6 +28,8 @@ public class PingTool : NetworkBehaviour public bool StartWithNetStateMonitorHidden = true; public KeyCode NetStatsMonitorToggle = KeyCode.Tab; #endif + public Text PingRateValue; + public Text PingToolHelp; public bool EnableConsoleLogging = true; public UnityEvent LogMessage; @@ -65,7 +67,7 @@ public void Reset() private Dictionary m_NextClientPingTime = new Dictionary(); private Dictionary m_ClientUpdateRoutines = new Dictionary(); private Slider m_Slider; - private Text m_PingRateValue; + private Canvas m_PingUICanvas; private bool m_PingRateUpdated; private float m_LastUpdatedTime; @@ -93,7 +95,6 @@ public ClientRtt GetClientRtt(ulong clientID) private void Awake() { m_Slider = GetComponentInChildren(); - m_PingRateValue = GetComponentInChildren(); m_PingUICanvas = GetComponentInChildren(); m_PingUICanvas.gameObject.SetActive(false); #if MULTIPLAYER_TOOLS @@ -102,6 +103,11 @@ private void Awake() m_NetStatsMonitor.Visible = false; } #endif + + if (PingToolHelp) + { + PingToolHelp.text = PingToolHelp.text.Replace("<>", $"<{NetStatsMonitorToggle}>"); + } } protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) @@ -156,11 +162,19 @@ public override void OnNetworkSpawn() InitializeAllClients(); NetworkManager.OnConnectionEvent += OnConnectionEvent; } + UpdatePingRateValue(m_PingRate.Value); - m_PingRateValue.text = $"{m_PingRate.Value}"; base.OnNetworkSpawn(); } + private void UpdatePingRateValue(int value) + { + if (PingRateValue) + { + PingRateValue.text = $"{m_PingRate.Value}"; + } + } + protected override void OnOwnershipChanged(ulong previous, ulong current) { if (current == NetworkManager.LocalClientId) @@ -182,7 +196,7 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) private void OnPingRateChanged(int previous, int current) { InitializePingClientTime(OwnerClientId); - m_PingRateValue.text = $"{current}"; + UpdatePingRateValue(current); } public override void OnNetworkDespawn() @@ -290,7 +304,7 @@ private void OnPingRateChanged() if (m_Slider.value != PingRate) { PingRate = (int)m_Slider.value; - m_PingRateValue.text = $"{PingRate}"; + UpdatePingRateValue(PingRate); // Flag the last time this value updated for the OwnerMonitorPingRateChange coroutine m_LastUpdatedTime = Time.realtimeSinceStartup; m_PingRateUpdated = true; @@ -377,6 +391,10 @@ private void Update() if (m_NetStatsMonitor && Input.GetKeyDown(NetStatsMonitorToggle)) { m_NetStatsMonitor.Visible = !m_NetStatsMonitor.Visible; + if (PingToolHelp) + { + PingToolHelp.gameObject.SetActive(!m_NetStatsMonitor.Visible); + } } } } @@ -422,12 +440,24 @@ private void InitializeClient(ulong clientId, bool hasEntry) [Rpc(SendTo.SpecifiedInParams)] private void PingRpc(float timeSent, RpcParams rpcParams) { + // Exit early if despawning or shutdown + if (!IsSpawned || NetworkManager == null || !NetworkManager.IsListening || NetworkManager.ShutdownInProgress) + { + return; + } + PongRpc(Mathf.Abs(NetworkManager.ServerTime.TimeAsFloat - timeSent), RpcTarget.Single(rpcParams.Receive.SenderClientId, RpcTargetUse.Temp)); } [Rpc(SendTo.SpecifiedInParams)] private void PongRpc(float timeDelta, RpcParams rpcParams) { + // Exit early if despawning or shutdown + if (!IsSpawned || NetworkManager == null || !NetworkManager.IsListening || NetworkManager.ShutdownInProgress) + { + return; + } + UpdateClientRTT(rpcParams.Receive.SenderClientId, timeDelta); } @@ -448,7 +478,7 @@ private void UpdateClientRTT(ulong clientId, float timeDelta) } var pingEntry = m_ClientPingQueue[clientId]; - var ping = timeDelta - (latencyToTimeServer * 0.001f); + var ping = Mathf.Abs(timeDelta - (latencyToTimeServer * 0.001f)); pingEntry.PingTime += ping; pingEntry.UTP_RTT += currentRTT; pingEntry.ReceivedPongs++; diff --git a/Examples/PingTool/Assets/PingTool/PingTool.prefab b/Examples/PingTool/Assets/PingTool/PingTool.prefab index 84c17c5ced..b36922c89a 100644 --- a/Examples/PingTool/Assets/PingTool/PingTool.prefab +++ b/Examples/PingTool/Assets/PingTool/PingTool.prefab @@ -31,6 +31,7 @@ RectTransform: m_LocalScale: {x: 0, y: 0, z: 0} m_ConstrainProportionsScale: 0 m_Children: + - {fileID: 1251000097366145441} - {fileID: 6277513074320060017} - {fileID: 4052611471472719404} - {fileID: 4428071759692496771} @@ -543,7 +544,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: GlobalObjectIdHash: 1175888021 - InScenePlacedSourceGlobalObjectIdHash: 0 + InScenePlacedSourceGlobalObjectIdHash: 290216032 DeferredDespawnTick: 0 Ownership: 2 AlwaysReplicateAsRoot: 0 @@ -553,6 +554,8 @@ MonoBehaviour: SpawnWithObservers: 1 DontDestroyWithOwner: 1 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!114 &2592079281344418938 MonoBehaviour: m_ObjectHideFlags: 0 @@ -565,14 +568,108 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 1466a9d81cf83ff45bca7014cb93c010, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 PingRate: 2 StartWithNetStateMonitorHidden: 1 NetStatsMonitorToggle: 9 + PingRateValue: {fileID: 3165861447957584369} + PingToolHelp: {fileID: 7873216060197180850} EnableConsoleLogging: 0 LogMessage: m_PersistentCalls: - m_Calls: [] + m_Calls: + - m_Target: {fileID: 0} + m_TargetAssemblyTypeName: NetworkManagerHelper, Assembly-CSharp + m_MethodName: LogPingToolMessage + m_Mode: 0 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 m_NetStatsMonitor: {fileID: 1263430958893551039} +--- !u!1 &6370153036591290436 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1251000097366145441} + - component: {fileID: 277549501682696586} + - component: {fileID: 7873216060197180850} + m_Layer: 5 + m_Name: PingToolHelper + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1251000097366145441 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6370153036591290436} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8751044705753477979} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 132.5, y: 25.600006} + m_SizeDelta: {x: 260, y: 24} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &277549501682696586 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6370153036591290436} + m_CullTransparentMesh: 1 +--- !u!114 &7873216060197180850 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6370153036591290436} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 0.4606132, b: 0, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Hit To Show PingTool --- !u!1 &7349017088681708274 GameObject: m_ObjectHideFlags: 0 diff --git a/Examples/PingTool/Assets/Scenes/SampleScene.unity b/Examples/PingTool/Assets/Scenes/SampleScene.unity index 593d861048..b880ffe45e 100644 --- a/Examples/PingTool/Assets/Scenes/SampleScene.unity +++ b/Examples/PingTool/Assets/Scenes/SampleScene.unity @@ -42,7 +42,8 @@ RenderSettings: --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 12 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 m_GISettings: serializedVersion: 2 m_BounceScale: 1 @@ -145,7 +146,7 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 28232985} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 @@ -154,7 +155,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} - m_AnchoredPosition: {x: 0, y: 40} + m_AnchoredPosition: {x: 0, y: 25} m_SizeDelta: {x: 180, y: 30} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &28232987 @@ -222,6 +223,8 @@ MonoBehaviour: SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!114 &28232990 MonoBehaviour: m_ObjectHideFlags: 0 @@ -234,6 +237,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6637cd674efb56a48a3d4d545d23a8d3, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!1 &455857869 GameObject: m_ObjectHideFlags: 0 @@ -243,12 +247,10 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 455857870} - - component: {fileID: 455857873} - - component: {fileID: 455857871} - component: {fileID: 455857872} - - component: {fileID: 455857874} + - component: {fileID: 455857871} m_Layer: 0 - m_Name: NetworkManager + m_Name: ExtNetworkManager m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -307,32 +309,17 @@ MonoBehaviour: m_GameObject: {fileID: 455857869} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: e4d8d44c602b97b47ba488a40c66267c, type: 3} - m_Name: - m_EditorClassIdentifier: - ConnectionType: 1 - EnableVSync: 0 - TargetFrameRate: 100 - m_OriginalVSyncCount: 0 ---- !u!114 &455857873 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 455857869} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} + m_Script: {fileID: 11500000, guid: 9639343147b80384d98dd77ff33767d4, type: 3} m_Name: m_EditorClassIdentifier: + NetworkManagerExpanded: 1 NetworkConfig: ProtocolVersion: 0 NetworkTransport: {fileID: 455857871} PlayerPrefab: {fileID: 8921789205124766477, guid: 89b57e576a8d47643b2dbd45b1f8cab1, type: 3} Prefabs: NetworkPrefabsLists: - - {fileID: 11400000, guid: 3d25a2b1f6c12ee47bf7601c2edd7e70, type: 2} + - {fileID: 11400000, guid: aa82390bfdde2564f828b8e5be375282, type: 2} TickRate: 30 ClientConnectionBufferTimeout: 10 ConnectionApproval: 0 @@ -356,19 +343,11 @@ MonoBehaviour: OldPrefabList: [] RunInBackground: 1 LogLevel: 1 ---- !u!114 &455857874 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 455857869} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 09865cca13a4bec439963971d7296b02, type: 3} - m_Name: - m_EditorClassIdentifier: - NetworkManager: {fileID: 455857873} + ExtendedNetworkManagerExpanded: 1 + ConnectionType: 1 + TargetFrameRate: 100 + EnableVSync: 0 + m_OriginalVSyncCount: 1 --- !u!1 &479361661 GameObject: m_ObjectHideFlags: 0 @@ -417,12 +396,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} m_Name: m_EditorClassIdentifier: - m_UiScaleMode: 0 + m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 - m_ReferenceResolution: {x: 800, y: 600} + m_ReferenceResolution: {x: 1280, y: 720} m_ScreenMatchMode: 0 - m_MatchWidthOrHeight: 0 + m_MatchWidthOrHeight: 0.5 m_PhysicalUnit: 3 m_FallbackScreenDPI: 96 m_DefaultSpriteDPI: 96 @@ -576,6 +555,10 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: + - target: {fileID: 1251000097366145441, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} + propertyPath: m_AnchoredPosition.x + value: 134.3 + objectReference: {fileID: 0} - target: {fileID: 2569679938784265262, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} propertyPath: m_LocalPosition.x value: 0 @@ -620,14 +603,6 @@ PrefabInstance: propertyPath: LogMessage.m_PersistentCalls.m_Calls.Array.size value: 1 objectReference: {fileID: 0} - - target: {fileID: 2592079281344418938, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} - propertyPath: LogMessage.m_PersistentCalls.m_Calls.Array.data[0].m_Mode - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 2592079281344418938, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} - propertyPath: LogMessage.m_PersistentCalls.m_Calls.Array.data[0].m_Target - value: - objectReference: {fileID: 455857872} - target: {fileID: 2592079281344418938, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} propertyPath: LogMessage.m_PersistentCalls.m_Calls.Array.data[0].m_CallState value: 2 @@ -668,9 +643,13 @@ PrefabInstance: propertyPath: m_AnchorMax.y value: 0 objectReference: {fileID: 0} - - target: {fileID: 6057903054739576449, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} - propertyPath: m_LocalPosition.y - value: 0 + - target: {fileID: 7873216060197180850, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} + propertyPath: m_Text + value: <> Toggles PingTool + objectReference: {fileID: 0} + - target: {fileID: 7873216060197180850, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} + propertyPath: m_FontData.m_Alignment + value: 3 objectReference: {fileID: 0} - target: {fileID: 9070511216422219346, guid: 3f5e8ae5613c82a4f9f859c11a4f2e09, type: 3} propertyPath: GlobalObjectIdHash diff --git a/Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs b/Examples/PingTool/Assets/Scripts/ExtendedNetworkManager.cs similarity index 54% rename from Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs rename to Examples/PingTool/Assets/Scripts/ExtendedNetworkManager.cs index 3c06675415..2c291bbe89 100644 --- a/Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs +++ b/Examples/PingTool/Assets/Scripts/ExtendedNetworkManager.cs @@ -3,38 +3,107 @@ using System.Linq; using System.Threading.Tasks; using Unity.Netcode; -using Unity.Netcode.Examples.PingTool; -using Unity.Netcode.Transports.UTP; using Unity.Services.Authentication; using Unity.Services.Core; using Unity.Services.Multiplayer; using UnityEngine; +using SessionState = Unity.Services.Multiplayer.SessionState; +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(ExtendedNetworkManager), true)] +[CanEditMultipleObjects] +public class ExtendedNetworkManagerEditor : NetworkManagerEditor +{ + private SerializedProperty m_ConnectionType; + private SerializedProperty m_TargetFrameRate; + private SerializedProperty m_EnableVSync; + + public override void OnEnable() + { + m_ConnectionType = serializedObject.FindProperty(nameof(ExtendedNetworkManager.ConnectionType)); + m_TargetFrameRate = serializedObject.FindProperty(nameof(ExtendedNetworkManager.TargetFrameRate)); + m_EnableVSync = serializedObject.FindProperty(nameof(ExtendedNetworkManager.EnableVSync)); + base.OnEnable(); + } + + private void DisplayExtendedNetworkManagerProperties() + { + EditorGUILayout.PropertyField(m_ConnectionType); + EditorGUILayout.PropertyField(m_TargetFrameRate); + EditorGUILayout.PropertyField(m_EnableVSync); + } + + public override void OnInspectorGUI() + { + var extendedNetworkManager = target as ExtendedNetworkManager; + // Handle switching the appropriate connection type based on the network topology + // Host connectio type can be set for client-server and distributed authority + // Live Service can only be used with distributed authority + // Client-server can only be used with a host connection type + var connectionTypes = Enum.GetValues(typeof(ExtendedNetworkManager.ConnectionTypes)); + var connectionType = ExtendedNetworkManager.ConnectionTypes.LiveService; + if (m_ConnectionType.enumValueIndex > 0 && m_ConnectionType.enumValueIndex < connectionTypes.Length) + { + connectionType = (ExtendedNetworkManager.ConnectionTypes)connectionTypes.GetValue(m_ConnectionType.enumValueIndex); + } + void SetExpanded(bool expanded) { extendedNetworkManager.ExtendedNetworkManagerExpanded = expanded; }; + DrawFoldOutGroup(extendedNetworkManager.GetType(), DisplayExtendedNetworkManagerProperties, extendedNetworkManager.ExtendedNetworkManagerExpanded, SetExpanded); + + var updatedConnectedType = (ExtendedNetworkManager.ConnectionTypes)connectionTypes.GetValue(m_ConnectionType.enumValueIndex); + if (connectionType == updatedConnectedType && updatedConnectedType == ExtendedNetworkManager.ConnectionTypes.LiveService && extendedNetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + extendedNetworkManager.ConnectionType = ExtendedNetworkManager.ConnectionTypes.Host; + } + else if (connectionType == ExtendedNetworkManager.ConnectionTypes.Host && updatedConnectedType == ExtendedNetworkManager.ConnectionTypes.LiveService && extendedNetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + extendedNetworkManager.NetworkConfig.NetworkTopology = NetworkTopologyTypes.DistributedAuthority; + } + base.OnInspectorGUI(); + } +} +#endif -public class NetworkManagerHelper : MonoBehaviour + + +public class ExtendedNetworkManager : NetworkManager { - public static NetworkManagerHelper Instance; +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool ExtendedNetworkManagerExpanded; +#endif + + public static ExtendedNetworkManager Instance; public enum ConnectionTypes { - Service, + LiveService, Host, } - - [Tooltip("The connection type. When set to service, it will connect to the local service. When host, it will use the standard host connection.")] public ConnectionTypes ConnectionType; - [Tooltip("Enables or disables VSynch.")] - public bool EnableVSync = false; - - [Tooltip("Sets the desired target frame rate.")] public int TargetFrameRate = 100; + public bool EnableVSync = false; [HideInInspector] [SerializeField] private int m_OriginalVSyncCount; - private NetworkManager m_NetworkManager; +#if UNITY_EDITOR + + protected override void OnValidateComponent() + { + m_OriginalVSyncCount = QualitySettings.vSyncCount; + base.OnValidateComponent(); + } +#endif + private ISession m_CurrentSession; private string m_SessionName; @@ -50,19 +119,6 @@ private enum ConnectionStates private ConnectionStates m_ConnectionState; -#if UNITY_EDITOR - private void OnValidate() - { - m_NetworkManager = GetComponent(); - m_OriginalVSyncCount = QualitySettings.vSyncCount; - } -#endif - - public void LogPingToolMessage(ulong clientId, PingTool.ClientRtt clientRtt) - { - LogMessage($"[Client-{clientId}] Ping: {clientRtt.Ping * 1000}ms | RTT: {clientRtt.RTT}ms", 5); - } - public static string GetRandomString(int length) { var r = new System.Random(); @@ -79,11 +135,14 @@ private void Awake() { Screen.SetResolution((int)(Screen.currentResolution.width * 0.40f), (int)(Screen.currentResolution.height * 0.40f), FullScreenMode.Windowed); SetFrameRate(TargetFrameRate, EnableVSync); - m_NetworkManager = GetComponent(); + SetSingleton(); } private async void Start() { + OnClientConnectedCallback += OnClientConnected; + OnClientDisconnectCallback += OnClientDisconnect; + OnConnectionEvent += OnClientConnectionEvent; if (UnityServices.Instance != null && UnityServices.Instance.State != ServicesInitializationState.Initialized) { await UnityServices.InitializeAsync(); @@ -101,6 +160,13 @@ private async void Start() } } + private void OnDestroy() + { + OnClientConnectedCallback -= OnClientConnected; + OnClientDisconnectCallback -= OnClientDisconnect; + OnConnectionEvent -= OnClientConnectionEvent; + } + private void SignedIn() { AuthenticationService.Instance.SignedIn -= SignedIn; @@ -119,54 +185,32 @@ private void OnDrawLiveServiceGUI() if (GUILayout.Button("Create or Connect To Session")) { - m_NetworkManager.NetworkConfig.UseCMBService = true; - m_NetworkManager.OnClientStopped += OnClientStopped; - m_NetworkManager.OnClientStarted += OnClientStarted; + NetworkConfig.UseCMBService = true; + OnClientStopped += ClientStopped; + OnClientStarted += ClientStarted; m_SessionTask = ConnectThroughLiveService(); m_ConnectionState = ConnectionStates.Connecting; LogMessage($"Connecting to session {m_SessionName}..."); } } - private void OnDrawLocalServiceGUI() - { - var unityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; - GUILayout.Label("IP Address:", GUILayout.Width(100)); - unityTransport.ConnectionData.Address = GUILayout.TextField(unityTransport.ConnectionData.Address, GUILayout.Width(100)); - - GUILayout.Label("Port:", GUILayout.Width(100)); - var portString = GUILayout.TextField(unityTransport.ConnectionData.Port.ToString(), GUILayout.Width(100)); - ushort.TryParse(portString, out unityTransport.ConnectionData.Port); - - // CMB distributed authority services just "connects" with no host, client, or server option (all are clients) - if (GUILayout.Button("Start Client")) - { - m_NetworkManager.NetworkConfig.UseCMBService = true; - m_NetworkManager.OnClientStopped += OnClientStopped; - m_NetworkManager.OnClientStarted += OnClientStarted; - m_NetworkManager.StartClient(); - m_ConnectionState = ConnectionStates.Connecting; - } - } - private void OnDrawDAHostGUI() { if (GUILayout.Button("Start Host")) { - m_NetworkManager.OnClientStopped += OnClientStopped; - m_NetworkManager.OnClientStarted += OnClientStarted; - m_NetworkManager.StartHost(); + OnClientStopped += ClientStopped; + OnClientStarted += ClientStarted; + StartHost(); } if (GUILayout.Button("Start Client")) { - m_NetworkManager.OnClientStopped += OnClientStopped; - m_NetworkManager.OnClientStarted += OnClientStarted; - m_NetworkManager.StartClient(); + OnClientStopped += ClientStopped; + OnClientStarted += ClientStarted; + StartClient(); } } - private void OnUpdateGUIDisconnected() { GUILayout.BeginArea(new Rect(10, 10, 300, 800)); @@ -174,14 +218,14 @@ private void OnUpdateGUIDisconnected() GUILayout.Label("Session Name", GUILayout.Width(100)); var connectionType = ConnectionType; - if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer && connectionType != ConnectionTypes.Service) + if (NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer && connectionType != ConnectionTypes.Host) { connectionType = ConnectionTypes.Host; } switch (connectionType) { - case ConnectionTypes.Service: + case ConnectionTypes.LiveService: { OnDrawLiveServiceGUI(); break; @@ -194,22 +238,41 @@ private void OnUpdateGUIDisconnected() } GUILayout.EndArea(); + + GUILayout.BeginArea(new Rect(10, Display.main.renderingHeight - 40, Display.main.renderingWidth - 10, 30)); + var scenesPreloaded = new System.Text.StringBuilder(); + scenesPreloaded.Append("Scenes Preloaded: "); + for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) + { + var scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + scenesPreloaded.Append($"[{scene.name}]"); + } + GUILayout.Label(scenesPreloaded.ToString()); + GUILayout.EndArea(); } private void OnUpdateGUIConnected() { - GUILayout.BeginArea(new Rect(10, 10, 800, 40)); - if (m_NetworkManager.CMBServiceConnection) + if (CMBServiceConnection) { + GUILayout.BeginArea(new Rect(10, 10, 800, 800)); GUILayout.Label($"Session: {m_SessionName}"); + GUILayout.EndArea(); } else { - var unityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; - GUILayout.Label($"IP: {unityTransport.ConnectionData.Address} : {unityTransport.ConnectionData.Port}"); - } + GUILayout.BeginArea(new Rect(10, 10, 800, 800)); + if (DistributedAuthorityMode) + { + GUILayout.Label($"DAHosted Session"); + } + else + { + GUILayout.Label($"Client-Server Session"); + } - GUILayout.EndArea(); + GUILayout.EndArea(); + } GUILayout.BeginArea(new Rect(Display.main.renderingWidth - 160, 10, 150, 80)); @@ -222,7 +285,7 @@ private void OnUpdateGUIConnected() } else { - m_NetworkManager.Shutdown(); + Shutdown(); } } @@ -262,16 +325,16 @@ private void OnGUI() GUILayout.EndArea(); } - private void OnClientStarted() + private void ClientStarted() { - m_NetworkManager.OnClientStarted -= OnClientStarted; + OnClientStarted -= ClientStarted; m_ConnectionState = ConnectionStates.Connected; LogMessage($"Connected to session {m_SessionName}."); } - private void OnClientStopped(bool isHost) + private void ClientStopped(bool isHost) { - m_NetworkManager.OnClientStopped -= OnClientStopped; + OnClientStopped -= ClientStopped; m_ConnectionState = ConnectionStates.None; m_SessionTask = null; m_CurrentSession = null; @@ -293,7 +356,6 @@ private async Task ConnectThroughLiveService() catch (Exception e) { LogMessage($"{e.Message}"); - m_ConnectionState = ConnectionStates.None; Debug.LogException(e); } return null; @@ -315,6 +377,21 @@ private void Update() } } + private void OnClientConnectionEvent(NetworkManager networkManager, ConnectionEventData eventData) + { + LogMessage($"[{Time.realtimeSinceStartup}] Connection event {eventData.EventType} for Client-{eventData.ClientId}."); + } + + private void OnClientConnected(ulong clientId) + { + LogMessage($"[{Time.realtimeSinceStartup}] Connected event invoked for Client-{clientId}."); + } + + private void OnClientDisconnect(ulong clientId) + { + LogMessage($"[{Time.realtimeSinceStartup}] Disconnected event invoked for Client-{clientId}."); + } + private List m_MessageLogs = new List(); private class MessageLog @@ -343,7 +420,7 @@ public void LogMessage(string msg, float timeToLive = 10.0f) Debug.Log(msg); } - public NetworkManagerHelper() + public ExtendedNetworkManager() { Instance = this; } diff --git a/Examples/PingTool/Assets/Scripts/ExtendedNetworkManager.cs.meta b/Examples/PingTool/Assets/Scripts/ExtendedNetworkManager.cs.meta new file mode 100644 index 0000000000..80b3f767c1 --- /dev/null +++ b/Examples/PingTool/Assets/Scripts/ExtendedNetworkManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9639343147b80384d98dd77ff33767d4 \ No newline at end of file diff --git a/Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs.meta b/Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs.meta deleted file mode 100644 index 6c3e265735..0000000000 --- a/Examples/PingTool/Assets/Scripts/NetworkManagerHelper.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e4d8d44c602b97b47ba488a40c66267c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs b/Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs deleted file mode 100644 index 32ef99b677..0000000000 --- a/Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Unity.Netcode; -using UnityEngine; - -public class OnDisconnectNotify : MonoBehaviour -{ - public NetworkManager NetworkManager; - private void Start() - { - NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; - NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; - NetworkManager.OnConnectionEvent += OnConnectionEvent; - } - - private void OnConnectionEvent(NetworkManager networkManager, ConnectionEventData eventData) - { - NetworkManagerHelper.Instance.LogMessage($"[{Time.realtimeSinceStartup}] Connection event {eventData.EventType} for Client-{eventData.ClientId}."); - } - - private void OnClientConnectedCallback(ulong clientId) - { - NetworkManagerHelper.Instance.LogMessage($"[{Time.realtimeSinceStartup}] Connected event invoked for Client-{clientId}."); - } - - private void OnClientDisconnectCallback(ulong clientId) - { - NetworkManagerHelper.Instance.LogMessage($"[{Time.realtimeSinceStartup}] Disconnected event invoked for Client-{clientId}."); - } - - private void OnDestroy() - { - NetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback; - NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; - NetworkManager.OnConnectionEvent -= OnConnectionEvent; - } -} diff --git a/Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs.meta b/Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs.meta deleted file mode 100644 index 12549aa4f5..0000000000 --- a/Examples/PingTool/Assets/Scripts/OnDisconnectNotify.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 09865cca13a4bec439963971d7296b02 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Examples/PingTool/Assets/Scripts/ServerHostClientText.cs b/Examples/PingTool/Assets/Scripts/ServerHostClientText.cs index e2ff41dfa6..74dfdd7ada 100644 --- a/Examples/PingTool/Assets/Scripts/ServerHostClientText.cs +++ b/Examples/PingTool/Assets/Scripts/ServerHostClientText.cs @@ -8,7 +8,7 @@ public class ServerHostClientText : NetworkBehaviour private Color m_Color; private Color m_ColorAlpha; - private Vector3 m_LocalPosition; + private Vector3 m_AnchoredPosition3D; public void SetColor(Color color) { @@ -19,7 +19,8 @@ public void SetColor(Color color) private void Awake() { - m_LocalPosition = transform.localPosition; + // Get the anchored 3D position + m_AnchoredPosition3D = (transform as RectTransform).anchoredPosition3D; m_DisplayText = GetComponent(); } @@ -45,7 +46,8 @@ public override void OnNetworkSpawn() m_DisplayText.text = $"Client-{NetworkManager.LocalClientId}"; } } - transform.localPosition = m_LocalPosition; + // Apply the AnchoredPosition3D to account for any adjustments in resolution/size + (transform as RectTransform).anchoredPosition3D = m_AnchoredPosition3D; } public override void OnNetworkDespawn() diff --git a/Examples/PingTool/Packages/manifest.json b/Examples/PingTool/Packages/manifest.json index 82de3dac2a..45f1b14b65 100644 --- a/Examples/PingTool/Packages/manifest.json +++ b/Examples/PingTool/Packages/manifest.json @@ -3,22 +3,23 @@ "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ads": "4.4.2", - "com.unity.ai.navigation": "2.0.0", + "com.unity.ai.navigation": "2.0.4", "com.unity.analytics": "3.8.1", - "com.unity.collab-proxy": "2.4.3", + "com.unity.collab-proxy": "2.5.2", "com.unity.feature.development": "1.0.2", "com.unity.ide.visualstudio": "2.0.22", - "com.unity.multiplayer.playmode": "1.1.0", - "com.unity.multiplayer.tools": "2.1.0", - "com.unity.netcode.gameobjects": "2.0.0-pre.3", - "com.unity.services.multiplayer": "1.0.0-pre.1", - "com.unity.test-framework": "1.4.4", + "com.unity.multiplayer.center": "1.0.0", + "com.unity.multiplayer.playmode": "1.3.0", + "com.unity.multiplayer.tools": "2.2.1", + "com.unity.netcode.gameobjects": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", + "com.unity.services.multiplayer": "1.0.0", + "com.unity.test-framework": "1.4.5", "com.unity.timeline": "1.8.7", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.9", "com.unity.transport": "2.3.0", "com.unity.ugui": "2.0.0", "com.unity.visualscripting": "1.9.4", - "com.unity.xr.legacyinputhelpers": "2.1.10", + "com.unity.xr.legacyinputhelpers": "2.1.11", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", From a0f1165420d646bec09a86962adf5f96e48448f8 Mon Sep 17 00:00:00 2001 From: Frank Luong <100299641+fluong6@users.noreply.github.com> Date: Mon, 21 Oct 2024 10:59:20 -0400 Subject: [PATCH 117/236] chore: NGO 2.1.1 merge back to develop NGO develop 2.0.0 (#3105) * bump version to 2.1.1 * updating changelog * update changelog --- com.unity.netcode.gameobjects/CHANGELOG.md | 11 +++++------ com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index bd7b7b8793..c335f3cdfb 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,7 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed -## [2.1.0] - 2024-10-17 +## [2.1.1] - 2024-10-18 ### Added @@ -28,12 +28,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103) -- Fixed issue with nested `NetworkTransform` components clearing their initial prefab settings when in owner authoritative mode on the server side while using a client-server network topology which resulted in improper synchronization of the nested `NetworkTransform` components. (#3099) +- Fixed an issue where nested `NetworkTransform` components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) -- Fixed issue where a newly synchronizing client would be synchronized with the current `NetworkVariable` values always which could cause issues with collections if there were any pending state updates. Now, when initially synchronizing a client, if a `NetworkVariable` has a pending state update it will serialize the previously known value(s) to the synchronizing client so when the pending updates are sent they aren't duplicate values on the newly connected client side. (#3081) +- Fixed an issue where newly synchronizing clients would always receive current `NetworkVariable` values, potentially causing issues with collections if there were pending updates. Now, pending state updates serialize previous values to avoid duplicates on new clients. (#3081) - Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3081) -- Fixed issue with `NetworkVariable` collections where transferring ownership to another client would not update the new owner's previous value to the most current value which could cause the last/previous added value to be detected as a change when adding or removing an entry (as long as the entry removed was not the last/previously added value). (#3081) +- Fixed an issue where transferring ownership of `NetworkVariable` collections didn't update the new owner’s previous value, causing the last added value to be detected as a change during additions or removals. (#3081) - Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3081) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) @@ -42,8 +42,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097) -- Changed `NetworkVariableDeltaMessage` so the server now forwards delta state updates (owner write permission based from a client) to other clients immediately as opposed to keeping a `NetworkVariable` or `NetworkList` dirty and processing them at the end of the frame or potentially on the next network tick. (#3081) - +- Updated `NetworkVariableDeltaMessage` so the server now forwards delta state updates from clients immediately, instead of waiting until the end of the frame or the next network tick. (#3081) ## [2.0.0] - 2024-09-12 diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 8ad3bffa32..a632de0172 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.1.0", + "version": "2.1.1", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From a115c83128fa58701842ddc502f1f6915af6cd18 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 21 Oct 2024 17:01:33 -0500 Subject: [PATCH 118/236] fix: NetworkAnimator parameter writebuffer limits causes exception (#3108) * fix Pre-calculate the required parameter write size needed as opposed to using the legacy constant value. * fix Decided this all could be handled during the Awake method with no need to pre-calculate during validation. Also made adjustments to have a more precise calculation for the size needed. * update Adding change log entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Components/NetworkAnimator.cs | 50 ++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c335f3cdfb..13d8143f6a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. + ### Changed ## [2.1.1] - 2024-10-18 diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 16b77ca5af..e122e8f46f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -230,6 +230,7 @@ private void BuildDestinationToTransitionInfoTable() } } + #if UNITY_EDITOR private void ParseStateMachineStates(int layerIndex, ref AnimatorController animatorController, ref AnimatorStateMachine stateMachine) { @@ -263,7 +264,6 @@ private void ParseStateMachineStates(int layerIndex, ref AnimatorController anim { case AnimatorControllerParameterType.Trigger: { - if (transition.destinationStateMachine != null) { var destinationStateMachine = transition.destinationStateMachine; @@ -297,6 +297,7 @@ private void ParseStateMachineStates(int layerIndex, ref AnimatorController anim } } } + #endif /// @@ -507,10 +508,6 @@ protected virtual bool OnIsServerAuthoritative() return NetworkManager ? !NetworkManager.DistributedAuthorityMode : true; } - // Animators only support up to 32 parameters - // TODO: Look into making this a range limited property - private const int k_MaxAnimationParams = 32; - private int[] m_TransitionHash; private int[] m_AnimationHash; private float[] m_LayerWeights; @@ -534,7 +531,7 @@ private unsafe struct AnimatorParamCache } // 128 bytes per Animator - private FastBufferWriter m_ParameterWriter = new FastBufferWriter(k_MaxAnimationParams * sizeof(float), Allocator.Persistent); + private FastBufferWriter m_ParameterWriter; private NativeArray m_CachedAnimatorParameters; @@ -586,6 +583,14 @@ public override void OnDestroy() protected virtual void Awake() { + if (!m_Animator) + { +#if !UNITY_EDITOR + Debug.LogError($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly."); +#endif + return; + } + int layers = m_Animator.layerCount; // Initializing the below arrays for everyone handles an issue // when running in owner authoritative mode and the owner changes. @@ -615,6 +620,9 @@ protected virtual void Awake() } } + // The total initialization size calculated for the m_ParameterWriter write buffer. + var totalParameterSize = sizeof(uint); + // Build our reference parameter values to detect when they change var parameters = m_Animator.parameters; m_CachedAnimatorParameters = new NativeArray(parameters.Length, Allocator.Persistent); @@ -655,7 +663,37 @@ protected virtual void Awake() } m_CachedAnimatorParameters[i] = cacheParam; + + // Calculate parameter sizes (index + type size) + switch (parameter.type) + { + case AnimatorControllerParameterType.Int: + { + totalParameterSize += sizeof(int) * 2; + break; + } + case AnimatorControllerParameterType.Bool: + case AnimatorControllerParameterType.Trigger: + { + // Bool is serialized to 1 byte + totalParameterSize += sizeof(int) + 1; + break; + } + case AnimatorControllerParameterType.Float: + { + totalParameterSize += sizeof(int) + sizeof(float); + break; + } + } } + + if (m_ParameterWriter.IsInitialized) + { + m_ParameterWriter.Dispose(); + } + + // Create our parameter write buffer for serialization + m_ParameterWriter = new FastBufferWriter(totalParameterSize, Allocator.Persistent); } /// From c696b58efb598cd7263b7d3294e7e4c3a11573ae Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 22 Oct 2024 11:34:49 -0500 Subject: [PATCH 119/236] fix: Distributed Authority codec tests not using message version (#3109) * fix Fix issue where the CodecTestHooks was not serializing the message using the current message version. * update also updating the test project to use the release version of Unity 6 * fix When sending, always use the message version. --- .../DistributedAuthorityCodecTests.cs | 14 ++++++++------ testproject/ProjectSettings/ProjectVersion.txt | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 397d4f0747..398d0403ee 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -205,13 +205,14 @@ public IEnumerator NamedMessage() [UnityTest] public IEnumerator NetworkVariableDelta() { + var component = Client.LocalClient.PlayerObject.GetComponent(); var message = new NetworkVariableDeltaMessage { - NetworkObjectId = 0, - NetworkBehaviourIndex = 1, - DeliveryMappedNetworkVariableIndex = new HashSet { 2, 3, 4 }, + NetworkObjectId = Client.LocalClient.PlayerObject.NetworkObjectId, + NetworkBehaviourIndex = component.NetworkBehaviourId, + DeliveryMappedNetworkVariableIndex = new HashSet { 0, 1 }, TargetClientId = 5, - NetworkBehaviour = Client.LocalClient.PlayerObject.GetComponent(), + NetworkBehaviour = component, }; yield return SendMessage(ref message); @@ -569,7 +570,7 @@ public IEnumerator SceneEventMessageObjectSceneChanged() private IEnumerator SendMessage(ref T message) where T : INetworkMessage { - Client.MessageManager.SetVersion(k_ClientId, XXHash.Hash32(typeof(T).FullName), 0); + Client.MessageManager.SetVersion(k_ClientId, XXHash.Hash32(typeof(T).FullName), message.Version); var clientIds = new NativeArray(1, Allocator.Temp); clientIds[0] = k_ClientId; @@ -625,7 +626,8 @@ public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDeliver } var writer = new FastBufferWriter(1024, Allocator.Temp); - message.Serialize(writer, 0); + // Serialize the message using the known message version + message.Serialize(writer, message.Version); var testName = TestContext.CurrentContext.Test.Name; if (!m_ExpectedMessages.ContainsKey(testName)) diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index 59d74ae16d..11b73a4be2 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.10f1 -m_EditorVersionWithRevision: 6000.0.10f1 (413673acabac) +m_EditorVersion: 6000.0.23f1 +m_EditorVersionWithRevision: 6000.0.23f1 (1c4764c07fb4) From b64117e9bcac02be276aa9a6fe50c5228de0ea3b Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 24 Oct 2024 15:52:47 -0500 Subject: [PATCH 120/236] fix: Player removed from observers when player object despawns (#3110) * fix Remove legacy observer removal code. * update adding change log entry. * fix Assure owner is assigned as an observer to the player. Make sure observers are not removed from despawned players. * fix Make sure to take spawn with observers into account when assigning the OwnerClientId as an observer. * test adding validation test for the fix. * update Adding change log PR numbers to the two entries. (left one out in PR-3108) * test-fix Fixing weird issue with the PlayerSpawnDespawn and ghost NetworkManagers. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 +- .../Runtime/Components/NetworkAnimator.cs | 13 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 47 +++--- .../Tests/Runtime/PlayerObjectTests.cs | 149 ++++++++++++++++++ 4 files changed, 179 insertions(+), 33 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 13d8143f6a..2c2df03c8c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,7 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. +- Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110) +- Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3108) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index e122e8f46f..7f2c4ae23a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -306,10 +306,6 @@ private void ParseStateMachineStates(int layerIndex, ref AnimatorController anim private void BuildTransitionStateInfoList() { #if UNITY_EDITOR - if (UnityEditor.EditorApplication.isUpdating || UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) - { - return; - } if (m_Animator == null) { return; @@ -1268,10 +1264,11 @@ internal void UpdateAnimationState(AnimationState animationState) NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) sub-table does not contain destination state ({animationState.DestinationStateHash})!"); } } - else if (NetworkManager.LogLevel == LogLevel.Developer) - { - NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!"); - } + // For reference, it is valid to have no transition information + //else if (NetworkManager.LogLevel == LogLevel.Developer) + //{ + // NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!"); + //} } else if (animationState.Transition && animationState.CrossFade) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index c17e05653b..61442eb08a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -88,6 +88,13 @@ private void AddPlayerObject(NetworkObject playerObject) } } + // Only if spawn with observers is set or we are using a distributed authority network topology and this is the client's player should we add + // the owner as an observer. + if (playerObject.SpawnWithObservers || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClientId == playerObject.OwnerClientId)) + { + playerObject.Observers.Add(playerObject.OwnerClientId); + } + m_PlayerObjects.Add(playerObject); if (!m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId)) { @@ -110,8 +117,9 @@ internal void UpdateNetworkClientPlayer(NetworkObject playerObject) if (playerNetworkClient.PlayerObject != null && m_PlayerObjects.Contains(playerNetworkClient.PlayerObject)) { // Just remove the previous player object but keep the assigned observers of the NetworkObject - RemovePlayerObject(playerNetworkClient.PlayerObject, true); + RemovePlayerObject(playerNetworkClient.PlayerObject); } + // Now update the associated NetworkClient's player object NetworkManager.ConnectionManager.ConnectedClients[playerObject.OwnerClientId].AssignPlayerObject(ref playerObject); AddPlayerObject(playerObject); @@ -120,7 +128,7 @@ internal void UpdateNetworkClientPlayer(NetworkObject playerObject) /// /// Removes a player object and updates all other players' observers list /// - private void RemovePlayerObject(NetworkObject playerObject, bool keepObservers = false) + private void RemovePlayerObject(NetworkObject playerObject, bool destroyingObject = false) { if (!playerObject.IsPlayerObject) { @@ -141,16 +149,21 @@ private void RemovePlayerObject(NetworkObject playerObject, bool keepObservers = } } - // If we want to keep the observers, then exit early - if (keepObservers) + if (NetworkManager.ConnectionManager.ConnectedClients.ContainsKey(playerObject.OwnerClientId) && destroyingObject) { - return; + NetworkManager.ConnectionManager.ConnectedClients[playerObject.OwnerClientId].PlayerObject = null; } - foreach (var player in m_PlayerObjects) - { - player.Observers.Remove(playerObject.OwnerClientId); - } + // If we want to keep the observers, then exit early + //if (keepObservers) + //{ + // return; + //} + + //foreach (var player in m_PlayerObjects) + //{ + // player.Observers.Remove(playerObject.OwnerClientId); + //} } internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId) @@ -1550,23 +1563,9 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec SpawnedObjectsList.Remove(networkObject); } - // DANGO-TODO: When we fix the issue with observers not being applied to NetworkObjects, - // (client connect/disconnect) we can remove this hacky way of doing this. - // Basically, when a player disconnects and/or is destroyed they are removed as an observer from all other client - // NetworkOject instances. - if (networkObject.IsPlayerObject && !networkObject.IsOwner && networkObject.OwnerClientId != NetworkManager.LocalClientId) - { - foreach (var netObject in SpawnedObjects) - { - if (netObject.Value.Observers.Contains(networkObject.OwnerClientId)) - { - netObject.Value.Observers.Remove(networkObject.OwnerClientId); - } - } - } if (networkObject.IsPlayerObject) { - RemovePlayerObject(networkObject); + RemovePlayerObject(networkObject, destroyGameObject); } // Always clear out the observers list when despawned diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs index e382bcfa88..4ffb78395b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PlayerObjectTests.cs @@ -1,4 +1,7 @@ using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -184,4 +187,150 @@ public IEnumerator PlayerSpawnPosition() } } } + + /// + /// This test validates that when a player is spawned it has all observers + /// properly set and the spawned and player object lists are properly populated. + /// It also validates that when a player is despawned and the client is still connected + /// that the client maintains its observers for other players. + /// + [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class PlayerSpawnAndDespawnTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 4; + + private StringBuilder m_ErrorLog = new StringBuilder(); + + public PlayerSpawnAndDespawnTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + private bool ValidateObservers(ulong playerClientId, NetworkObject player, ref List networkManagers) + { + foreach (var networkManager in networkManagers) + { + if (player != null && player.IsSpawned) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(player.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} does not contain a spawned object entry {nameof(NetworkObject)}-{player.NetworkObjectId} for Client-{playerClientId}!"); + return false; + } + + var playerClone = networkManager.SpawnManager.SpawnedObjects[player.NetworkObjectId]; + foreach (var clientId in networkManager.ConnectedClientsIds) + { + if (!playerClone.IsNetworkVisibleTo(clientId)) + { + m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} failed visibility check for Client-{clientId} on {nameof(NetworkObject)}-{player.NetworkObjectId} for Client-{playerClientId}!"); + return false; + } + } + + var foundPlayerClone = (NetworkObject)null; + foreach (var playerObject in networkManager.SpawnManager.PlayerObjects) + { + if (playerObject.OwnerClientId == playerClientId && playerObject.NetworkObjectId == player.NetworkObjectId) + { + foundPlayerClone = playerObject; + break; + } + } + if (!foundPlayerClone) + { + m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} does not contain a player entry for {nameof(NetworkObject)}-{player.NetworkObjectId} for Client-{playerClientId}!"); + return false; + } + + } + else + { + // If the player client in question is despawned, then no NetworkManager instance + // should contain a clone of that (or the client's NetworkManager instance as well) + foreach (var playerClone in networkManager.SpawnManager.PlayerObjects) + { + if (playerClone.OwnerClientId == playerClientId) + { + m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} contains a player {nameof(NetworkObject)}-{playerClone.NetworkObjectId} for Client-{playerClientId} when it should be despawned!"); + return false; + } + } + } + } + return true; + } + + private bool ValidateAllClientPlayerObservers() + { + var networkManagers = new List(); + + if (m_ServerNetworkManager.IsHost) + { + networkManagers.Add(m_ServerNetworkManager); + } + foreach (var networkManager in m_ClientNetworkManagers) + { + networkManagers.Add(networkManager); + } + + m_ErrorLog.Clear(); + var success = true; + foreach (var networkManager in networkManagers) + { + var spawnedOrNot = networkManager.LocalClient.PlayerObject == null ? "despawned" : "spawned"; + m_ErrorLog.AppendLine($"Validating Client-{networkManager.LocalClientId} {spawnedOrNot} player."); + if (networkManager.LocalClient == null) + { + m_ErrorLog.AppendLine($"No {nameof(NetworkClient)} found for Client-{networkManager.LocalClientId}!"); + success = false; + break; + } + if (!ValidateObservers(networkManager.LocalClientId, networkManager.LocalClient.PlayerObject, ref networkManagers)) + { + m_ErrorLog.AppendLine($"Client-{networkManager.LocalClientId} validation pass failed."); + success = false; + break; + } + } + networkManagers.Clear(); + return success; + } + + + [UnityTest] + public IEnumerator PlayerSpawnDespawn() + { + // Validate all observers are properly set with all players spawned + yield return WaitForConditionOrTimeOut(ValidateAllClientPlayerObservers); + AssertOnTimeout($"First Validation Failed:\n {m_ErrorLog}"); + var selectedClient = m_ClientNetworkManagers[Random.Range(0, m_ClientNetworkManagers.Count() - 1)]; + var playerSelected = selectedClient.LocalClient.PlayerObject; + + if (m_DistributedAuthority) + { + playerSelected.Despawn(false); + } + else + { + m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerSelected.NetworkObjectId].Despawn(true); + } + + // Validate all observers are properly set with one of the players despawned + yield return WaitForConditionOrTimeOut(ValidateAllClientPlayerObservers); + AssertOnTimeout($"Second Validation Failed:\n {m_ErrorLog}"); + + if (m_DistributedAuthority) + { + playerSelected.SpawnAsPlayerObject(selectedClient.LocalClientId, false); + } + else + { + SpawnPlayerObject(m_ServerNetworkManager.NetworkConfig.PlayerPrefab, selectedClient); + } + + // Validate all observers are properly set when the client's player is respawned. + yield return WaitForConditionOrTimeOut(ValidateAllClientPlayerObservers); + AssertOnTimeout($"Third Validation Failed:\n {m_ErrorLog}"); + } + } } From d709df42050580392a3a6caeeff919e54a8ac935 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 4 Nov 2024 13:08:40 -0600 Subject: [PATCH 121/236] fix: send to not owner server warning message (#3111) * fix When sending to not owner, use the not authority target when using a distributed authority network topology. * fix When sending to owner, use the authority target when using a distributed authority network topology. * update adding change log entry * fix This resolves the remaining issues with universal rpcs when using a distributed authority network topology. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../RpcTargets/ClientsAndHostRpcTarget.cs | 3 +- .../Messaging/RpcTargets/NotOwnerRpcTarget.cs | 10 ++++ .../RpcTargets/NotServerRpcTarget.cs | 8 ++- .../Messaging/RpcTargets/OwnerRpcTarget.cs | 11 ++++ .../Messaging/RpcTargets/ServerRpcTarget.cs | 53 ++++++++++++++++++- 6 files changed, 83 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2c2df03c8c..fd4ae80745 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111) - Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110) - Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3108) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs index 286b0b0134..373e43f4fe 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs @@ -17,7 +17,8 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, // ClientsAndHost sends to everyone who runs any client logic // So if the server is a host, this target includes it (as hosts run client logic) // If the server is not a host, this target leaves it out, ergo the selection of NotServer. - if (behaviour.NetworkManager.ServerIsHost) + // If we are in distributed authority mode and connected to a service, then send to all clients. + if (behaviour.NetworkManager.ServerIsHost || (m_NetworkManager.DistributedAuthorityMode && m_NetworkManager.CMBServiceConnection)) { m_UnderlyingTarget = behaviour.RpcTarget.Everyone; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs index e4f64f0530..348c1af313 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs @@ -4,12 +4,14 @@ internal class NotOwnerRpcTarget : BaseRpcTarget { private IGroupRpcTarget m_GroupSendTarget; private ServerRpcTarget m_ServerRpcTarget; + private NotAuthorityRpcTarget m_NotAuthorityRpcTarget; private LocalSendRpcTarget m_LocalSendRpcTarget; public override void Dispose() { m_ServerRpcTarget.Dispose(); m_LocalSendRpcTarget.Dispose(); + m_NotAuthorityRpcTarget.Dispose(); if (m_GroupSendTarget != null) { m_GroupSendTarget.Target.Dispose(); @@ -19,6 +21,13 @@ public override void Dispose() internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { + // Not owner is the same as not authority in distributed authority mode + if (m_NetworkManager.DistributedAuthorityMode) + { + m_NotAuthorityRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + return; + } + if (m_GroupSendTarget == null) { if (behaviour.IsServer) @@ -86,6 +95,7 @@ internal NotOwnerRpcTarget(NetworkManager manager) : base(manager) { m_ServerRpcTarget = new ServerRpcTarget(manager); m_LocalSendRpcTarget = new LocalSendRpcTarget(manager); + m_NotAuthorityRpcTarget = new NotAuthorityRpcTarget(manager); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs index 30b745e68c..3ac0b231a9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs @@ -51,7 +51,13 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, continue; } - if (clientId == behaviour.NetworkManager.LocalClientId) + // If we are in distributed authority mode and connected to the service, then we exclude the owner/authority from the list + if (m_NetworkManager.DistributedAuthorityMode && m_NetworkManager.CMBServiceConnection && clientId == behaviour.OwnerClientId) + { + continue; + } + + if (clientId == m_NetworkManager.LocalClientId) { m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams); continue; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs index 7c7829402d..e22c588ef3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs @@ -5,9 +5,12 @@ internal class OwnerRpcTarget : BaseRpcTarget private IIndividualRpcTarget m_UnderlyingTarget; private LocalSendRpcTarget m_LocalRpcTarget; private ServerRpcTarget m_ServerRpcTarget; + private AuthorityRpcTarget m_AuthorityRpcTarget; public override void Dispose() { + m_AuthorityRpcTarget.Dispose(); + m_ServerRpcTarget.Dispose(); m_LocalRpcTarget.Dispose(); if (m_UnderlyingTarget != null) { @@ -18,6 +21,13 @@ public override void Dispose() internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { + // Sending to owner is the same as sending to authority in distributed authority mode + if (m_NetworkManager.DistributedAuthorityMode) + { + m_AuthorityRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + return; + } + if (behaviour.OwnerClientId == behaviour.NetworkManager.LocalClientId) { m_LocalRpcTarget.Send(behaviour, ref message, delivery, rpcParams); @@ -49,6 +59,7 @@ internal OwnerRpcTarget(NetworkManager manager) : base(manager) { m_LocalRpcTarget = new LocalSendRpcTarget(manager); m_ServerRpcTarget = new ServerRpcTarget(manager); + m_AuthorityRpcTarget = new AuthorityRpcTarget(manager); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 8ad53fb992..0db593f23b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -1,8 +1,10 @@ +using Unity.Collections; namespace Unity.Netcode { internal class ServerRpcTarget : BaseRpcTarget { protected BaseRpcTarget m_UnderlyingTarget; + protected ProxyRpcTarget m_ProxyRpcTarget; public override void Dispose() { @@ -11,13 +13,62 @@ public override void Dispose() m_UnderlyingTarget.Dispose(); m_UnderlyingTarget = null; } + + if (m_ProxyRpcTarget != null) + { + m_ProxyRpcTarget.Dispose(); + m_ProxyRpcTarget = null; + } } internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) { + // For distributed authority the "server" is considered the authority of the object if (behaviour.NetworkManager.DistributedAuthorityMode && behaviour.NetworkManager.CMBServiceConnection) { - UnityEngine.Debug.LogWarning("[Invalid Target] There is no server to send to when in Distributed Authority mode!"); + // If the local instance is the owner, then invoke the message locally on this behaviour + if (behaviour.IsOwner) + { + var context = new NetworkContext + { + SenderId = m_NetworkManager.LocalClientId, + Timestamp = m_NetworkManager.RealTimeProvider.RealTimeSinceStartup, + SystemOwner = m_NetworkManager, + // header information isn't valid since it's not a real message. + // RpcMessage doesn't access this stuff so it's just left empty. + Header = new NetworkMessageHeader(), + SerializedHeaderSize = 0, + MessageSize = 0 + }; + using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None); + message.ReadBuffer = tempBuffer; + message.Handle(ref context); + // If enabled, then add the RPC metrics for this +#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE + int length = tempBuffer.Length; + if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) + { + m_NetworkManager.NetworkMetrics.TrackRpcSent( + m_NetworkManager.LocalClientId, + behaviour.NetworkObject, + rpcMethodName, + behaviour.__getTypeName(), + length); + } +#endif + } + else // Otherwise, send a proxied message to the owner of the object + { + if (m_ProxyRpcTarget == null) + { + m_ProxyRpcTarget = new ProxyRpcTarget(behaviour.OwnerClientId, m_NetworkManager); + } + else + { + m_ProxyRpcTarget.SetClientId(behaviour.OwnerClientId); + } + m_ProxyRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } return; } From 8e494c4af527d5ff468ab99263b7ce25a6bce32b Mon Sep 17 00:00:00 2001 From: Ken Roecks Date: Tue, 5 Nov 2024 17:29:17 -0800 Subject: [PATCH 122/236] upgrade changes --- .../DefaultSceneManagerHandler.cs | 24 +++++++++-- .../SceneManagement/ISceneManagerHandler.cs | 2 +- .../SceneManagement/NetworkSceneManager.cs | 41 ++++++++++++++----- .../SceneManagement/SceneEventProgress.cs | 9 ++++ .../Runtime/IntegrationTestSceneHandler.cs | 6 +-- 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 2a1a676780..2340327262 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using TrollKing.Core; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; @@ -14,6 +15,8 @@ namespace Unity.Netcode /// internal class DefaultSceneManagerHandler : ISceneManagerHandler { + private static NetworkLogScope s_Log = new NetworkLogScope(nameof(DefaultSceneManagerHandler)); + private Scene m_InvalidScene = new Scene(); internal struct SceneEntry @@ -27,6 +30,7 @@ internal struct SceneEntry public AsyncOperationHandle LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress) { + s_Log.Info(() => $"Loading scene '{sceneName}'..."); AsyncOperationHandle operation = default; if (loadSceneMode == LoadSceneMode.Single) { @@ -56,11 +60,23 @@ public AsyncOperationHandle LoadSceneAsync(string sceneName, Load return operation; } - public AsyncOperationHandle UnloadSceneAsync(SceneInstance scene, SceneEventProgress sceneEventProgress) + public AsyncOperationHandle UnloadSceneAsync(NetworkSceneManager.SceneData scene, SceneEventProgress sceneEventProgress) { - var operation = Addressables.UnloadSceneAsync(scene); - sceneEventProgress.SetAsyncOperation(operation); - return operation; + if (scene.SceneInstance.HasValue) + { + var operation = Addressables.UnloadSceneAsync(scene.SceneInstance.Value); + sceneEventProgress.SetAsyncOperation(operation); + return operation; + } + else + { + s_Log.Error(() => $"Unloaded a scene that wasn't loaded with addressables '{scene.SceneInstance}'"); + var unloadOp = SceneManager.UnloadSceneAsync(scene.SceneReference); + AsyncOperationHandle operation = default; + sceneEventProgress.SetAsyncOperation(operation); + return operation; + } + } /// diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs index ff4700df71..9f52e28da6 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/ISceneManagerHandler.cs @@ -14,7 +14,7 @@ public interface ISceneManagerHandler { AsyncOperationHandle LoadSceneAsync(string sceneAssetKey, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress); - AsyncOperationHandle UnloadSceneAsync(SceneInstance scene, SceneEventProgress sceneEventProgress); + AsyncOperationHandle UnloadSceneAsync(NetworkSceneManager.SceneData scene, SceneEventProgress sceneEventProgress); void PopulateLoadedScenes(ref Dictionary scenesLoaded, NetworkManager networkManager = null); Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager = null); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 85ceec2c8d..171e8be7f5 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -9,6 +9,7 @@ using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; namespace Unity.Netcode @@ -472,6 +473,7 @@ internal bool UpdateServerClientSceneHandle(int serverHandle, int clientHandle, { if (!ServerSceneHandleToClientSceneHandle.ContainsKey(serverHandle)) { + Log.Info(() => $"Adding Server Scene Handle {clientHandle} {localScene.name}"); ServerSceneHandleToClientSceneHandle.Add(serverHandle, clientHandle); } else if (!IsRestoringSession) @@ -481,6 +483,7 @@ internal bool UpdateServerClientSceneHandle(int serverHandle, int clientHandle, if (!ClientSceneHandleToServerSceneHandle.ContainsKey(clientHandle)) { + Log.Info(() => $"Adding Client Scene Handle {clientHandle} {localScene.name}"); ClientSceneHandleToServerSceneHandle.Add(clientHandle, serverHandle); } else if (!IsRestoringSession) @@ -505,6 +508,7 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) { if (ServerSceneHandleToClientSceneHandle.ContainsKey(serverHandle)) { + Log.Info(() => $"Remove ServerSceneHandleToClientSceneHandle {clientHandle} {serverHandle}"); ServerSceneHandleToClientSceneHandle.Remove(serverHandle); } else @@ -514,6 +518,7 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) if (ClientSceneHandleToServerSceneHandle.ContainsKey(clientHandle)) { + Log.Info(() => $"Remove ClientSceneHandleToServerSceneHandle {clientHandle} {serverHandle}"); ClientSceneHandleToServerSceneHandle.Remove(clientHandle); } else @@ -523,6 +528,7 @@ internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle) if (ScenesLoaded.ContainsKey(clientHandle)) { + Log.Info(() => $"Remove ScenesLoaded {clientHandle} {serverHandle}"); ScenesLoaded.Remove(clientHandle); } else @@ -1248,7 +1254,7 @@ public SceneEventProgressStatus UnloadScene(Scene scene) AsyncOperationHandle sceneUnload; if (ScenesLoaded[sceneHandle].SceneInstance.HasValue) { - sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle].SceneInstance.Value, sceneEventProgress); + sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress); } else { @@ -1318,7 +1324,7 @@ private void OnClientUnloadScene(uint sceneEventId) AsyncOperationHandle sceneUnload; if (ScenesLoaded[sceneHandle].SceneInstance.HasValue) { - sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle].SceneInstance.Value, sceneEventProgress); + sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress); } else { @@ -1436,7 +1442,7 @@ internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) foreach (var keyHandleEntry in ScenesLoaded) { // Validate the scene as well as ignore the DDOL (which will have a negative buildIndex) - if (currentActiveScene.name != keyHandleEntry.Value.SceneReference.name) + if (currentActiveScene.name != keyHandleEntry.Value.SceneReference.name && keyHandleEntry.Value.SceneReference != DontDestroyOnLoadScene) { var sceneEventProgress = new SceneEventProgress(NetworkManager) { @@ -1444,16 +1450,17 @@ internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) OnSceneEventCompleted = EmptySceneUnloadedOperation }; - if (ClientSceneHandleToServerSceneHandle.ContainsKey(keyHandleEntry.Value.SceneReference.handle)) + if (ClientSceneHandleToServerSceneHandle.TryGetValue(keyHandleEntry.Value.SceneReference.handle, out var serverSceneHandle)) { - var serverSceneHandle = ClientSceneHandleToServerSceneHandle[keyHandleEntry.Value.SceneReference.handle]; ServerSceneHandleToClientSceneHandle.Remove(serverSceneHandle); } + Log.Info(() => $"Remove ClientSceneHandleToServerSceneHandle {keyHandleEntry.Value.SceneReference.handle}"); ClientSceneHandleToServerSceneHandle.Remove(keyHandleEntry.Value.SceneReference.handle); - var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value.SceneInstance.Value, sceneEventProgress); + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress); SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value.SceneReference, LoadSceneMode.Additive, sceneUnload); + } } // clear out our scenes loaded list @@ -2810,11 +2817,23 @@ internal void MoveObjectsToDontDestroyOnLoad() // Only move dynamically spawned NetworkObjects with no parent as the children will follow if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) { - UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); - // When temporarily migrating to the DDOL, adjust the network and origin scene handles so no messages are generated - // about objects being moved to a new scene. - networkObject.NetworkSceneHandle = ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; - networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; + try + { + Object.DontDestroyOnLoad(networkObject.gameObject); + // When temporarily migrating to the DDOL, adjust the network and origin scene handles so no messages are generated + // about objects being moved to a new scene. + networkObject.NetworkSceneHandle = ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; + networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; + } + catch (Exception e) + { + string allEntities = ""; + foreach (var kvp in ClientSceneHandleToServerSceneHandle) + { + allEntities += $"{kvp.Key}={kvp.Value},"; + } + Log.Error(() => $"Failed to move object={networkObject} to DDOL [{allEntities}] e={e}"); + } } } else if (networkObject.HasAuthority) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index 35d606058c..5b1858b362 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; +using TrollKing.Core; using UnityEngine; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; @@ -70,6 +71,8 @@ public enum SceneEventProgressStatus /// public class SceneEventProgress { + private static NetworkLogScope Log = new NetworkLogScope(nameof(SceneEventProgress)); + /// /// List of clientIds of those clients that is done loading the scene. /// @@ -279,6 +282,12 @@ public AsyncOperationHandle GetHandle() /// public void SetAsyncOperation(AsyncOperationHandle asyncOperation) { + if (!asyncOperation.IsValid()) + { + Log.Error(() => $"Async Operation handle is invalid!"); + return; + } + // Debug.Log($"[SceneEventProgress] SetAsyncOperation "); m_AsyncOperation = asyncOperation; m_AsyncOperation.Completed += new Action>(asyncOp2 => diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index b84faf1c70..a97454a5d3 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -331,16 +331,16 @@ public AsyncOperationHandle LoadSceneAsync(string sceneAssetKey, return default; } - public AsyncOperationHandle UnloadSceneAsync(SceneInstance scene, SceneEventProgress sceneEventProgress) + public AsyncOperationHandle UnloadSceneAsync(NetworkSceneManager.SceneData scene, SceneEventProgress sceneEventProgress) { // Server and non NetcodeIntegrationTest tests use the generic unload scene method if (!NetcodeIntegrationTest.IsRunning) { - return GenericUnloadSceneAsync(scene, sceneEventProgress); + return GenericUnloadSceneAsync(scene.SceneInstance.Value, sceneEventProgress); } else // NetcodeIntegrationTest Clients always get added to the jobs queue { - AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Unloading }); + AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene.SceneInstance.Value, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Unloading }); } // This is OK to return a "nothing" AsyncOperation since we are simulating client loading return default; From 789f3753b5e9161372ec093ffe059e84f132ad45 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 6 Nov 2024 14:42:28 -0600 Subject: [PATCH 123/236] fix: NetworkTransport early and post late updates (#3113) * fix Break out the UnityTransport's Update method into two methods: EarlyUpdate and PostLateUpdate. * update Modifications to NetworkManager to invoke the newly adding EarlyUpdate and PostLateUpdate methods. * update Adjusting the job handle name. Migrating the m_FlushSendJobHandle.Complete(); outside of the driver IsCreated check to avoid any potential job safety issues. * style removing unused Update method and removing the Unity.Netcode.Transports.UTP namespace reference. * update Removing the job handle stuff as that could potentially break users trying to get RTT during OnGUI. * test fix Adjusting the UnityTransport tests to simulate the NetworkManager invoking the EarlyUpdate and PostLateUpdate methods. Adding event processing in shutdown primarily for integration testing to handle any pending events prior to shutting down the transport. This resolves the issue with the UnitTransportConnectionTests failing in areas where it was just waiting and not invoking the EarlyUpdate and PostLateUpdate methods. * test - fix Approaching the issue differently by adding a UnityTransportTestComponent that registers with the playerloop update stages needed to properly handle message processing. * test Removing the unused additional script and namespace reference. * test removing some more unused code and the namespace reference. * style removing some CRs and unused local var. * test - fix Removing whitespace issue. Setting velocity to zero after moving and after waiting. * update Adding change log entries. Found the culprit with the transform ownership instability. Moving an object can give it velocity which if there is enough time that passes between updates then fixed update will apply enough velocity motion to move the object and it will continue to move. Increased mass and linearDampening and setting the linear velocity to zero in the appropriate places. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 + .../Runtime/Core/NetworkManager.cs | 8 + .../Runtime/Transports/NetworkTransport.cs | 39 +++ .../Runtime/Transports/UTP/UnityTransport.cs | 41 ++- .../NetworkTransformOwnershipTests.cs | 136 +++++++-- .../UnityTransportConnectionTests.cs | 15 +- .../Transports/UnityTransportTestHelpers.cs | 55 +++- .../Runtime/Transports/UnityTransportTests.cs | 20 +- .../SceneTransitioningBase1.unity | 270 +++++++++++------- 9 files changed, 422 insertions(+), 165 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index fd4ae80745..22bef68281 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,8 +10,11 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `NetworkTransport.OnEarlyUpdate` and `NetworkTransport.OnPostLateUpdate` methods to provide more control over handling transport related events at the start and end of each frame. (#3113) + ### Fixed +- Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`. (#3113) - Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111) - Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110) - Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3108) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index de08ae0ebf..56a82bc33f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -291,6 +291,10 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) case NetworkUpdateStage.EarlyUpdate: { UpdateTopology(); + + // Handle processing any new connections or transport events + NetworkConfig.NetworkTransport.EarlyUpdate(); + ConnectionManager.ProcessPendingApprovals(); ConnectionManager.PollAndHandleNetworkEvents(); @@ -298,6 +302,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) AnticipationSystem.SetupForUpdate(); MessageManager.ProcessIncomingMessageQueue(); + MessageManager.CleanupDisconnectedClients(); AnticipationSystem.ProcessReanticipation(); } @@ -379,6 +384,9 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed. MetricsManager.UpdateMetrics(); + // Handle sending any pending transport messages + NetworkConfig.NetworkTransport.PostLateUpdate(); + // TODO: Determine a better way to handle this NetworkObject.VerifyParentingStatus(); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs index b624120269..d647aeab17 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs @@ -107,6 +107,45 @@ protected void InvokeOnTransportEvent(NetworkEvent eventType, ulong clientId, Ar /// /// optionally pass in NetworkManager public abstract void Initialize(NetworkManager networkManager = null); + /// + /// Invoked by NetworkManager at the beginning of its EarlyUpdate. + /// For order of operations see: + /// + /// Useful to handle processing any transport-layer events such as processing inbound messages or changes in connection state(s). + /// + protected virtual void OnEarlyUpdate() + { + + } + + /// + /// Invoked by NetworkManager at the beginning of its EarlyUpdate + /// + internal void EarlyUpdate() + { + OnEarlyUpdate(); + } + + /// + /// Invoked by NetworkManager towards the end of the PostLateUpdate. + /// For order of operations see: + /// + /// + /// Useful to handle any end of frame transport tasks such as sending queued transport messages. + /// + protected virtual void OnPostLateUpdate() + { + + } + + /// + /// Invoked by NetworkManager towards the end of the PostLateUpdate + /// + internal void PostLateUpdate() + { + OnPostLateUpdate(); + } + protected virtual NetworkTopologyTypes OnCurrentTopology() { return NetworkTopologyTypes.ClientServer; diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 0237517f01..60e49d0479 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -944,17 +944,13 @@ private bool ProcessEvent() return false; } - private void Update() + /// + /// Handles accepting new connections and processing transport events. + /// + protected override void OnEarlyUpdate() { if (m_Driver.IsCreated) { - foreach (var kvp in m_SendQueue) - { - SendBatchedMessages(kvp.Key, kvp.Value); - } - - m_Driver.ScheduleUpdate().Complete(); - if (m_ProtocolType == ProtocolType.RelayUnityTransport && m_Driver.GetRelayConnectionStatus() == RelayConnectionStatus.AllocationInvalid) { Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " + @@ -964,15 +960,38 @@ private void Update() return; } + m_Driver.ScheduleUpdate().Complete(); + + // Process any new connections while (AcceptConnection() && m_Driver.IsCreated) { ; } + // Process any transport events (i.e. connect, disconnect, data, etc) while (ProcessEvent() && m_Driver.IsCreated) { ; } + } + base.OnEarlyUpdate(); + } + + /// + /// Handles sending any queued batched messages. + /// + protected override void OnPostLateUpdate() + { + if (m_Driver.IsCreated) + { + foreach (var kvp in m_SendQueue) + { + SendBatchedMessages(kvp.Key, kvp.Value); + } + + // Schedule a flush send as the last transport action for the + // current frame. + m_Driver.ScheduleFlushSend(default).Complete(); #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 if (m_NetworkManager) @@ -981,6 +1000,7 @@ private void Update() } #endif } + base.OnPostLateUpdate(); } private void OnDestroy() @@ -1452,6 +1472,11 @@ public override void Shutdown() { if (m_Driver.IsCreated) { + while (ProcessEvent() && m_Driver.IsCreated) + { + ; + } + // Flush all send queues to the network. NGO can be configured to flush its message // queue on shutdown. But this only calls the Send() method, which doesn't actually // get anything to the network. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 31105efadd..ca402c6095 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -36,13 +36,25 @@ protected override void OnServerAndClientsCreated() { VerifyObjectIsSpawnedOnClient.ResetObjectTable(); m_ClientNetworkTransformPrefab = CreateNetworkObjectPrefab("OwnerAuthorityTest"); + var clientNetworkObject = m_ClientNetworkTransformPrefab.GetComponent(); + // When running in distributed authority mode, make the NetworkObject transferable + clientNetworkObject.SetOwnershipStatus(m_DistributedAuthority ? NetworkObject.OwnershipStatus.Transferable : NetworkObject.OwnershipStatus.None); var clientNetworkTransform = m_ClientNetworkTransformPrefab.AddComponent(); + clientNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner; clientNetworkTransform.Interpolate = false; clientNetworkTransform.UseHalfFloatPrecision = false; var rigidBody = m_ClientNetworkTransformPrefab.AddComponent(); rigidBody.useGravity = false; + rigidBody.maxDepenetrationVelocity = 0; + rigidBody.mass = 100; + rigidBody.linearDamping = 100; rigidBody.interpolation = RigidbodyInterpolation.None; rigidBody.maxLinearVelocity = 0; + rigidBody.detectCollisions = false; + rigidBody.position = Vector3.zero; + rigidBody.rotation = Quaternion.identity; + rigidBody.transform.position = Vector3.zero; + rigidBody.transform.rotation = Quaternion.identity; // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results @@ -51,11 +63,22 @@ protected override void OnServerAndClientsCreated() m_ClientNetworkTransformPrefab.AddComponent(); m_NetworkTransformPrefab = CreateNetworkObjectPrefab("ServerAuthorityTest"); - var networkTransform = m_NetworkTransformPrefab.AddComponent(); + var networkObject = m_ClientNetworkTransformPrefab.GetComponent(); + // When running in distributed authority mode, make the NetworkObject transferable + networkObject.SetOwnershipStatus(m_DistributedAuthority ? NetworkObject.OwnershipStatus.Transferable : NetworkObject.OwnershipStatus.None); + var networkTransform = m_NetworkTransformPrefab.AddComponent(); rigidBody = m_NetworkTransformPrefab.AddComponent(); rigidBody.useGravity = false; + rigidBody.maxDepenetrationVelocity = 0; + rigidBody.linearDamping = 100; + rigidBody.mass = 100; rigidBody.interpolation = RigidbodyInterpolation.None; rigidBody.maxLinearVelocity = 0; + rigidBody.detectCollisions = false; + rigidBody.position = Vector3.zero; + rigidBody.rotation = Quaternion.identity; + rigidBody.transform.position = Vector3.zero; + rigidBody.transform.rotation = Quaternion.identity; // NOTE: We don't use a sphere collider for this integration test because by the time we can // assure they don't collide and skew the results the NetworkObjects are already synchronized // with skewed results @@ -276,10 +299,12 @@ void LogOwnerRigidBody(int stage) }; if (m_MotionModel == MotionModels.UseRigidbody) { + TestClientNetworkTransform.EnableLogState(m_EnableVerboseDebug); var ownerRigidbody = ownerInstance.GetComponent(); ownerRigidbody.Move(valueSetByOwner, rotation); + yield return new WaitForFixedUpdate(); ownerRigidbody.linearVelocity = Vector3.zero; - yield return s_DefaultWaitForTick; + yield return new WaitForFixedUpdate(); ownerInstance.transform.localScale = valueSetByOwner; } else @@ -312,15 +337,22 @@ void LogOwnerRigidBody(int stage) VerifyObjectIsSpawnedOnClient.ResetObjectTable(); if (m_DistributedAuthority) { - ownerInstance.NetworkObject.ChangeOwnership(networkManagerNonOwner.LocalClientId); + Assert.True(nonOwnerInstance.OwnerClientId != networkManagerNonOwner.LocalClientId, $"Non-Owner Client-{networkManagerNonOwner.LocalClientId} was already the owner prior to changing ownership!"); + nonOwnerInstance.NetworkObject.ChangeOwnership(networkManagerNonOwner.LocalClientId); + nonOwnerInstance.GetComponent().linearVelocity = Vector3.zero; + Assert.True(nonOwnerInstance.OwnerClientId == networkManagerNonOwner.LocalClientId, $"Client-{networkManagerNonOwner.LocalClientId} failed to change ownership!"); + + LogNonOwnerRigidBody(3); + yield return WaitForConditionOrTimeOut(() => ownerInstance.GetComponent().OwnerClientId == networkManagerNonOwner.LocalClientId); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for original owner {networkManagerOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!"); } else { m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent(), networkManagerNonOwner.LocalClientId, true); + LogNonOwnerRigidBody(3); + yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent().OwnerClientId == networkManagerNonOwner.LocalClientId); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!"); } - LogNonOwnerRigidBody(3); - yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent().OwnerClientId == networkManagerNonOwner.LocalClientId); - Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!"); LogNonOwnerRigidBody(4); // Re-assign the ownership references and wait for the non-owner instance to be notified of ownership change @@ -331,6 +363,7 @@ void LogOwnerRigidBody(int stage) yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId) != null); nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId); Assert.NotNull(nonOwnerInstance); + Assert.True(!nonOwnerInstance.IsOwner, $"Ownership failed to change on Client-{networkManagerNonOwner.LocalClientId} side! Expected owner to be {networkManagerOwner.LocalClientId} but owner is still {networkManagerNonOwner.LocalClientId}!"); // Make sure the owner is not kinematic and the non-owner(s) are kinematic Assert.False(ownerInstance.GetComponent().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!"); @@ -344,7 +377,7 @@ void LogOwnerRigidBody(int stage) Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + - $"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}"); + $"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}\n {nonOwnerInstance.GetComponent().LogInfoBuilder}"); LogNonOwnerRigidBody(5); // Have the new owner change transform values and wait for those values to be applied on the non-owner side. @@ -358,6 +391,7 @@ void LogOwnerRigidBody(int stage) var ownerRigidbody = ownerInstance.GetComponent(); ownerRigidbody.Move(valueSetByOwner, rotation); LogOwnerRigidBody(2); + yield return new WaitForFixedUpdate(); ownerInstance.GetComponent().LogMotion = m_EnableVerboseDebug; nonOwnerInstance.GetComponent().LogMotion = m_EnableVerboseDebug; ownerRigidbody.linearVelocity = Vector3.zero; @@ -377,10 +411,11 @@ void LogOwnerRigidBody(int stage) LogOwnerRigidBody(4); LogNonOwnerRigidBody(7); } + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" + $"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" + $"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" + - $"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}"); + $"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}\n {nonOwnerInstance.GetComponent().LogInfoBuilder}"); // The last check is to verify non-owners cannot change transform values after ownership has changed nonOwnerInstance.transform.position = Vector3.zero; @@ -445,6 +480,12 @@ public IEnumerator ServerAuthoritativeTest() Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}"); } + protected override IEnumerator OnTearDown() + { + TestClientNetworkTransform.EnableLogState(false); + return base.OnTearDown(); + } + /// /// NetworkTransformOwnershipTests helper behaviour /// @@ -457,6 +498,10 @@ public static void ResetObjectTable() NetworkManagerRelativeSpawnedObjects.Clear(); } + /// + /// For testing, just before changing ownership the table is cleared to assure that + /// ownership tansfer occurs. This will add the new owner to the table. + /// public override void OnGainedOwnership() { if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) @@ -466,6 +511,10 @@ public override void OnGainedOwnership() base.OnGainedOwnership(); } + /// + /// For testing, just before changing ownership the table is cleared to assure that + /// ownership tansfer occurs. This will add the previous owner to the table. + /// public override void OnLostOwnership() { if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId)) @@ -515,28 +564,57 @@ public override void OnNetworkDespawn() [DisallowMultipleComponent] internal class TestClientNetworkTransform : NetworkTransform { - //public override void OnNetworkSpawn() - //{ - // base.OnNetworkSpawn(); - // CanCommitToTransform = IsOwner; - //} - - //protected override void Update() - //{ - // CanCommitToTransform = IsOwner; - // base.Update(); - // if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening)) - // { - // if (CanCommitToTransform) - // { - // TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time); - // } - // } - //} - - protected override bool OnIsServerAuthoritative() + public static void EnableLogState(bool enable) { - return false; + s_LogStateEnabled = enable; + TrackByStateId = enable; + } + + private static bool s_LogStateEnabled; + + internal StringBuilder LogInfoBuilder = new StringBuilder(); + + private void LogInfo(NetworkTransformState state) + { + if (s_LogStateEnabled) + { + LogInfoBuilder.AppendLine($"N:{name} | CID:{NetworkManager.LocalClientId} | SID: {state.StateId} | NT:{NetworkManager.ServerTime.Tick} | Pos: {transform.position} | Sc: {transform.localScale}"); + } + } + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + + if (s_LogStateEnabled) + { + LogInfoBuilder.AppendLine($"Ownership Changed: {previous} --> {current} | Position: {transform.position}"); + } + LogInfo(LocalAuthoritativeNetworkState); + base.OnOwnershipChanged(previous, current); + + // Assure no velocity is set on this object for this particular test + if (current == NetworkManager.LocalClientId) + { + GetComponent().linearVelocity = Vector3.zero; + } + } + + protected override void OnAuthorityPushTransformState(ref NetworkTransformState networkTransformState) + { + LogInfo(networkTransformState); + base.OnAuthorityPushTransformState(ref networkTransformState); + } + + protected override void OnBeforeUpdateTransformState() + { + LogInfo(LocalAuthoritativeNetworkState); + base.OnBeforeUpdateTransformState(); + } + + protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState) + { + LogInfo(newState); + base.OnNetworkTransformStateUpdated(ref oldState, ref newState); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs index ac4c0891c2..3e2c746082 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs @@ -33,7 +33,7 @@ public IEnumerator Cleanup() if (transport) { transport.Shutdown(); - UnityEngine.Object.DestroyImmediate(transport); + UnityEngine.Object.DestroyImmediate(transport.gameObject); } } @@ -42,6 +42,7 @@ public IEnumerator Cleanup() transportEvents?.Clear(); } + UnityTransportTestComponent.CleanUp(); yield return null; } @@ -59,6 +60,7 @@ public void DetectInvalidEndpoint() Assert.False(m_Clients[0].StartClient(), "Client failed to detect invalid endpoint!"); netcodeLogAssert.LogWasReceived(LogType.Error, $"Network listen address ({m_Server.ConnectionData.Address}) is Invalid!"); netcodeLogAssert.LogWasReceived(LogType.Error, $"Target server network address ({m_Clients[0].ConnectionData.Address}) is Invalid!"); + UnityTransportTestComponent.CleanUp(); } // Check connection with a single client. @@ -161,8 +163,6 @@ public IEnumerator ServerDisconnectMultipleClients() // Check that all clients got a Disconnect event. Assert.True(m_ClientsEvents.All(evs => evs.Count == 2)); Assert.True(m_ClientsEvents.All(evs => evs[1].Type == NetworkEvent.Disconnect)); - - yield return null; } // Check client disconnection from a single client. @@ -188,13 +188,11 @@ public IEnumerator ClientDisconnectMultipleClients() { InitializeTransport(out m_Server, out m_ServerEvents); m_Server.StartServer(); - for (int i = 0; i < k_NumClients; i++) { InitializeTransport(out m_Clients[i], out m_ClientsEvents[i]); m_Clients[i].StartClient(); } - yield return WaitForNetworkEvent(NetworkEvent.Connect, m_ClientsEvents[k_NumClients - 1]); // Disconnect a single client. @@ -207,14 +205,11 @@ public IEnumerator ClientDisconnectMultipleClients() { m_Clients[i].DisconnectLocalClient(); } - yield return WaitForNetworkEvent(NetworkEvent.Disconnect, m_ServerEvents, 5); // Check that we got the correct number of Disconnect events on the server. Assert.AreEqual(k_NumClients * 2, m_ServerEvents.Count); Assert.AreEqual(k_NumClients, m_ServerEvents.Count(e => e.Type == NetworkEvent.Disconnect)); - - yield return null; } // Check that server re-disconnects are no-ops. @@ -244,8 +239,6 @@ public IEnumerator RepeatedServerDisconnectsNoop() // Check we haven't received anything else on the client or server. Assert.AreEqual(m_ServerEvents.Count, previousServerEventsCount); Assert.AreEqual(m_ClientsEvents[0].Count, previousClientEventsCount); - - yield return null; } // Check that client re-disconnects are no-ops. @@ -275,8 +268,6 @@ public IEnumerator RepeatedClientDisconnectsNoop() // Check we haven't received anything else on the client or server. Assert.AreEqual(m_ServerEvents.Count, previousServerEventsCount); Assert.AreEqual(m_ClientsEvents[0].Count, previousClientEventsCount); - - yield return null; } // Check connection with different server/listen addresses. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs index 62ae2b561f..2523dd9257 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs @@ -43,7 +43,7 @@ public static void InitializeTransport(out UnityTransport transport, out List(); + transport = new GameObject().AddComponent(); transport.OnTransportEvent += logger.HandleEvent; transport.MaxPayloadSize = maxPayloadSize; @@ -91,5 +91,58 @@ public void HandleEvent(NetworkEvent type, ulong clientID, ArraySegment da }); } } + + internal class UnityTransportTestComponent : UnityTransport, INetworkUpdateSystem + { + private static List s_Instances = new List(); + + public static void CleanUp() + { + for (int i = s_Instances.Count - 1; i >= 0; i--) + { + var instance = s_Instances[i]; + instance.Shutdown(); + DestroyImmediate(instance.gameObject); + } + s_Instances.Clear(); + } + + /// + /// Simulate the being invoked so + /// and are invoked. + /// + public void NetworkUpdate(NetworkUpdateStage updateStage) + { + switch (updateStage) + { + case NetworkUpdateStage.EarlyUpdate: + { + EarlyUpdate(); + break; + } + case NetworkUpdateStage.PostLateUpdate: + { + PostLateUpdate(); + break; + } + } + } + + public override void Shutdown() + { + s_Instances.Remove(this); + base.Shutdown(); + this.UnregisterAllNetworkUpdates(); + } + + public override void Initialize(NetworkManager networkManager = null) + { + base.Initialize(networkManager); + this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate); + this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); + this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate); + s_Instances.Add(this); + } + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index 4a14206e02..d5b720dada 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -60,11 +60,11 @@ public IEnumerator Cleanup() // Need to destroy the GameObject (all assigned components will get destroyed too) UnityEngine.Object.DestroyImmediate(m_Client2.gameObject); } - m_ServerEvents?.Clear(); m_Client1Events?.Clear(); m_Client2Events?.Clear(); + UnityTransportTestComponent.CleanUp(); yield return null; } @@ -217,8 +217,6 @@ public IEnumerator MultipleSendsSingleFrame( Assert.AreEqual(33, m_ServerEvents[3].Data.First()); Assert.AreEqual(10, m_ServerEvents[3].Data.Count); - - yield return null; } // Check sending data to multiple clients. @@ -260,8 +258,6 @@ public IEnumerator SendMultipleClients( byte c1Data = m_Client1Events[1].Data.First(); byte c2Data = m_Client2Events[1].Data.First(); Assert.That((c1Data == 11 && c2Data == 22) || (c1Data == 22 && c2Data == 11)); - - yield return null; } // Check receiving data from multiple clients. @@ -299,8 +295,6 @@ public IEnumerator ReceiveMultipleClients( byte sData1 = m_ServerEvents[2].Data.First(); byte sData2 = m_ServerEvents[3].Data.First(); Assert.That((sData1 == 11 && sData2 == 22) || (sData1 == 22 && sData2 == 11)); - - yield return null; } // Check that we get disconnected when overflowing the reliable send queue. @@ -332,8 +326,6 @@ public IEnumerator DisconnectOnReliableSendQueueOverflow() Assert.AreEqual(2, m_Client1Events.Count); Assert.AreEqual(NetworkEvent.Disconnect, m_Client1Events[1].Type); - - yield return null; } // Check that it's fine to overflow the unreliable send queue (traffic is flushed on overflow). @@ -373,8 +365,6 @@ public IEnumerator SendCompletesOnUnreliableSendQueueOverflow() Assert.AreEqual(NetworkEvent.Data, m_ServerEvents[i].Type); Assert.AreEqual(1024, m_ServerEvents[i].Data.Count); } - - yield return null; } #if !UTP_TRANSPORT_2_0_ABOVE @@ -451,8 +441,6 @@ public IEnumerator SendQueuesFlushedOnShutdown([ValueSource("k_DeliveryParameter m_Client1.Shutdown(); yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents); - - yield return null; } [UnityTest] @@ -472,8 +460,6 @@ public IEnumerator SendQueuesFlushedOnLocalClientDisconnect([ValueSource("k_Deli m_Client1.DisconnectLocalClient(); yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents); - - yield return null; } [UnityTest] @@ -493,8 +479,6 @@ public IEnumerator SendQueuesFlushedOnRemoteClientDisconnect([ValueSource("k_Del m_Server.DisconnectRemoteClient(m_ServerEvents[0].ClientID); yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events); - - yield return null; } [UnityTest] @@ -514,8 +498,6 @@ public IEnumerator ReliablePayloadsCanBeLargerThanMaximum() m_Server.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable); yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events); - - yield return null; } } } diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity index b9af226321..bab567858b 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity @@ -13,7 +13,7 @@ OcclusionCullingSettings: --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 - serializedVersion: 9 + serializedVersion: 10 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 @@ -38,13 +38,12 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 12 - m_GIWorkflowMode: 1 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 m_GISettings: serializedVersion: 2 m_BounceScale: 1 @@ -67,9 +66,6 @@ LightmapSettings: m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 m_TextureCompression: 1 - m_FinalGather: 0 - m_FinalGatherFiltering: 1 - m_FinalGatherRayCount: 256 m_ReflectionCompression: 2 m_MixedBakeMode: 2 m_BakeBackend: 1 @@ -104,7 +100,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +113,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -154,7 +150,6 @@ RectTransform: m_Children: - {fileID: 1351730454} m_Father: {fileID: 290861172} - m_RootOrder: 11 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -173,6 +168,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -208,7 +204,6 @@ RectTransform: m_Children: - {fileID: 1347823142} m_Father: {fileID: 290861172} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} @@ -227,6 +222,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 244f0414ea8419b41ac51adb305d64b0, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_SwitchSceneButtonObject: {fileID: 1347823141} m_SceneToSwitchTo: SceneTransitioningBase2 m_EnableAutoSwitch: 0 @@ -245,10 +241,18 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: GlobalObjectIdHash: 412081913 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!1 &37242881 GameObject: m_ObjectHideFlags: 0 @@ -274,9 +278,8 @@ Light: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 37242881} m_Enabled: 1 - serializedVersion: 10 + serializedVersion: 11 m_Type: 1 - m_Shape: 0 m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} m_Intensity: 1 m_Range: 10 @@ -326,8 +329,12 @@ Light: m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 m_ShadowRadius: 0 m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 --- !u!4 &37242883 Transform: m_ObjectHideFlags: 0 @@ -335,13 +342,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 37242881} + serializedVersion: 2 m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 3, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} --- !u!224 &42803802 stripped RectTransform: @@ -381,7 +388,6 @@ RectTransform: m_Children: - {fileID: 1429502879} m_Father: {fileID: 362129048} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -457,7 +463,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 362129048} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -537,7 +542,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 212638130} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -609,9 +613,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 57392470} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &57392472 @@ -631,6 +643,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -671,13 +686,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 57392470} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -30.5, y: 0.49999994, z: 0} m_LocalScale: {x: 1, y: 3, z: 62} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1332123092} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &59926368 GameObject: @@ -710,7 +725,6 @@ RectTransform: m_Children: - {fileID: 1651938367} m_Father: {fileID: 290861172} - m_RootOrder: 8 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -729,6 +743,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -764,7 +779,6 @@ RectTransform: m_Children: - {fileID: 615497064} m_Father: {fileID: 1351730454} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -840,7 +854,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1640896166} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -965,7 +978,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -983,7 +998,6 @@ RectTransform: m_Children: - {fileID: 1865409449} m_Father: {fileID: 0} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -1022,7 +1036,6 @@ RectTransform: m_Children: - {fileID: 57138324} m_Father: {fileID: 1783165220} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -1143,7 +1156,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -1175,7 +1190,6 @@ RectTransform: - {fileID: 1362072618} - {fileID: 1783165220} m_Father: {fileID: 0} - m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -1213,7 +1227,6 @@ RectTransform: m_Children: - {fileID: 362129048} m_Father: {fileID: 290861172} - m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1232,6 +1245,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveScene2 m_SceneAsset: {fileID: 102900000, guid: c6a3d883c8253ee43bca4f2b03797d7b, type: 3} @@ -1267,7 +1281,6 @@ RectTransform: - {fileID: 1689223598} - {fileID: 1638885888} m_Father: {fileID: 884557066} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1362,9 +1375,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 336568645} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &336568647 @@ -1384,6 +1405,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -1424,13 +1448,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 336568645} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: -0.50000006, z: 0} m_LocalScale: {x: 60, y: 1, z: 60} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1332123092} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &348836178 GameObject: @@ -1463,7 +1487,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2084811628} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1540,7 +1563,6 @@ RectTransform: - {fileID: 44393280} - {fileID: 57065842} m_Father: {fileID: 291820796} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1640,7 +1662,6 @@ RectTransform: m_Children: - {fileID: 432733929} m_Father: {fileID: 1651938367} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -1716,7 +1737,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 418148061} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1792,7 +1812,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2079590080} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1868,7 +1887,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2058276876} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1957,9 +1975,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -1993,13 +2019,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 575203307} + serializedVersion: 2 m_LocalRotation: {x: 0.41890106, y: -0, z: -0, w: 0.9080319} m_LocalPosition: {x: 0, y: 42, z: -46} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 49.530003, y: 0, z: 0} --- !u!1 &599972120 GameObject: @@ -2032,7 +2058,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1588117328} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2112,7 +2137,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 125866603} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -2188,7 +2212,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1362072618} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2268,7 +2291,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1783165220} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2348,7 +2370,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1290928583} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -2424,7 +2445,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1398648428} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -2500,7 +2520,6 @@ RectTransform: m_Children: - {fileID: 300124662} m_Father: {fileID: 290861172} - m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -2519,6 +2538,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveScene1 m_SceneAsset: {fileID: 102900000, guid: 41a0239b0c49e2047b7063c822f0df8a, type: 3} @@ -2529,8 +2549,7 @@ LightingSettings: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: - serializedVersion: 4 - m_GIWorkflowMode: 1 + serializedVersion: 9 m_EnableBakedLightmaps: 1 m_EnableRealtimeLightmaps: 0 m_RealtimeEnvironmentLighting: 1 @@ -2540,6 +2559,8 @@ LightingSettings: m_UsingShadowmask: 1 m_BakeBackend: 1 m_LightmapMaxSize: 1024 + m_LightmapSizeFixed: 0 + m_UseMipmapLimits: 1 m_BakeResolution: 40 m_Padding: 2 m_LightmapCompression: 3 @@ -2553,13 +2574,11 @@ LightingSettings: m_FilterMode: 1 m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} m_ExportTrainingData: 0 + m_EnableWorkerProcessBaking: 1 m_TrainingDataDestination: TrainingData m_RealtimeResolution: 2 m_ForceWhiteAlbedo: 0 m_ForceUpdates: 0 - m_FinalGather: 0 - m_FinalGatherRayCount: 256 - m_FinalGatherFiltering: 1 m_PVRCulling: 1 m_PVRSampling: 1 m_PVRDirectSampleCount: 32 @@ -2568,8 +2587,8 @@ LightingSettings: m_PVREnvironmentReferencePointCount: 2048 m_LightProbeSampleCountMultiplier: 4 m_PVRBounces: 2 - m_PVRMinBounces: 1 - m_PVREnvironmentMIS: 1 + m_PVRMinBounces: 2 + m_PVREnvironmentImportanceSampling: 1 m_PVRFilteringMode: 1 m_PVRDenoiserTypeDirect: 1 m_PVRDenoiserTypeIndirect: 1 @@ -2583,7 +2602,7 @@ LightingSettings: m_PVRFilteringAtrousPositionSigmaDirect: 0.5 m_PVRFilteringAtrousPositionSigmaIndirect: 2 m_PVRFilteringAtrousPositionSigmaAO: 1 - m_PVRTiledBaking: 0 + m_RespectSceneVisibilityWhenBakingGI: 0 --- !u!1 &906714043 GameObject: m_ObjectHideFlags: 0 @@ -2615,7 +2634,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2019086800} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2669,6 +2687,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: 4518755925279129984, guid: 3a854a190ab5b1b4fb00bec725fdda9e, @@ -2731,6 +2750,11 @@ PrefabInstance: propertyPath: GlobalObjectIdHash value: 3537583461 objectReference: {fileID: 0} + - target: {fileID: 4518755925279129985, guid: 3a854a190ab5b1b4fb00bec725fdda9e, + type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 225455949 + objectReference: {fileID: 0} - target: {fileID: 4518755925279129986, guid: 3a854a190ab5b1b4fb00bec725fdda9e, type: 3} propertyPath: m_Name @@ -2742,6 +2766,9 @@ PrefabInstance: value: 1 objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 3a854a190ab5b1b4fb00bec725fdda9e, type: 3} --- !u!1 &1008611498 GameObject: @@ -2774,7 +2801,6 @@ RectTransform: m_Children: - {fileID: 1640896166} m_Father: {fileID: 290861172} - m_RootOrder: 9 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -2793,6 +2819,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -2828,8 +2855,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} m_Name: m_EditorClassIdentifier: - RunInBackground: 1 - LogLevel: 1 + NetworkManagerExpanded: 0 NetworkConfig: ProtocolVersion: 0 NetworkTransport: {fileID: 1024114723} @@ -2852,6 +2878,10 @@ MonoBehaviour: LoadSceneTimeOut: 120 SpawnTimeout: 1 EnableNetworkLogs: 1 + NetworkTopology: 0 + UseCMBService: 0 + AutoSpawnPlayerPrefabClientSide: 1 + NetworkProfilingMetrics: 1 OldPrefabList: - Override: 0 Prefab: {fileID: 771575417923360811, guid: c0a45bdb516f341498d933b7a7ed4fc1, @@ -2903,6 +2933,8 @@ MonoBehaviour: SourcePrefabToOverride: {fileID: 0} SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} + RunInBackground: 1 + LogLevel: 1 --- !u!4 &1024114720 Transform: m_ObjectHideFlags: 0 @@ -2910,13 +2942,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1024114717} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1024114721 MonoBehaviour: @@ -2957,6 +2989,8 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_ProtocolType: 0 + m_UseWebSockets: 0 + m_UseEncryption: 0 m_MaxPacketQueueSize: 128 m_MaxPayloadSize: 512000 m_HeartbeatTimeoutMS: 500 @@ -3001,6 +3035,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8c48ea35c67e64f7fac22a3f6831ca88, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 AutoSpawnEnable: 1 InitialSpawnDelay: 0.2 SpawnsPerSecond: 1 @@ -3027,13 +3062,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1113539278} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0.5, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1113539281 MonoBehaviour: @@ -3048,10 +3083,18 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: GlobalObjectIdHash: 1983031731 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!1 &1187680249 GameObject: m_ObjectHideFlags: 0 @@ -3083,7 +3126,6 @@ RectTransform: m_Children: - {fileID: 2019086800} m_Father: {fileID: 290861172} - m_RootOrder: 10 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -3102,6 +3144,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 475de064003ff104fb88b1fbccd0f417, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ActivateOnLoad: 0 m_SceneToLoad: AdditiveSceneMultiInstance m_SceneAsset: {fileID: 102900000, guid: 0ae94f636016d3b40bfbecad57d99553, type: 3} @@ -3136,7 +3179,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1651938367} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -3217,7 +3259,6 @@ RectTransform: m_Children: - {fileID: 833301795} m_Father: {fileID: 1640896166} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -3285,6 +3326,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1332123091} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0.000000059604645, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -3296,7 +3338,6 @@ Transform: - {fileID: 57392474} - {fileID: 1336081255} m_Father: {fileID: 0} - m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1336081251 GameObject: @@ -3325,9 +3366,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1336081251} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &1336081253 @@ -3347,6 +3396,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -3387,13 +3439,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1336081251} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 30.5, y: 0.49999994, z: 0} m_LocalScale: {x: 1, y: 3, z: 62} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1332123092} - m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1336892118 GameObject: @@ -3426,7 +3478,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1351730454} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -3508,7 +3559,6 @@ RectTransform: m_Children: - {fileID: 1846334315} m_Father: {fileID: 34066665} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} @@ -3641,7 +3691,6 @@ RectTransform: - {fileID: 125866603} - {fileID: 1336892119} m_Father: {fileID: 19371290} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -3741,7 +3790,6 @@ RectTransform: - {fileID: 2084811628} - {fileID: 703861692} m_Father: {fileID: 290861172} - m_RootOrder: 13 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -3840,7 +3888,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 290861172} - m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 0} @@ -3919,7 +3966,6 @@ RectTransform: m_Children: - {fileID: 1523424137} m_Father: {fileID: 2058276876} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -3958,7 +4004,6 @@ RectTransform: m_Children: - {fileID: 865202802} m_Father: {fileID: 2019086800} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -4034,7 +4079,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 44393280} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -4111,7 +4155,6 @@ RectTransform: - {fileID: 2079590080} - {fileID: 1489972349} m_Father: {fileID: 290861172} - m_RootOrder: 12 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -4210,7 +4253,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1456950069} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -4290,7 +4332,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1387688805} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -4366,7 +4407,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1889006547} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -4444,7 +4484,6 @@ RectTransform: m_Children: - {fileID: 599972121} m_Father: {fileID: 290861172} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -4576,7 +4615,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 300124662} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -4657,7 +4695,6 @@ RectTransform: - {fileID: 1290928583} - {fileID: 163541782} m_Father: {fileID: 1008611499} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -4757,7 +4794,6 @@ RectTransform: - {fileID: 418148061} - {fileID: 1210784442} m_Father: {fileID: 59926369} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -4857,7 +4893,6 @@ RectTransform: m_Children: - {fileID: 1691305196} m_Father: {fileID: 300124662} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -4933,7 +4968,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1689223598} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -5010,7 +5044,6 @@ RectTransform: - {fileID: 212638130} - {fileID: 725420114} m_Father: {fileID: 290861172} - m_RootOrder: 14 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -5138,13 +5171,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1834318145} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -431, y: -242.5, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 290861172} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1846334314 GameObject: @@ -5177,7 +5210,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1347823142} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -5253,9 +5285,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1857685343} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &1857685345 @@ -5275,6 +5315,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -5315,19 +5358,20 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1857685343} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0.49999994, z: -30.5} m_LocalScale: {x: 60, y: 3, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1332123092} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1001 &1865409448 PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 167044834} m_Modifications: - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, @@ -5441,6 +5485,9 @@ PrefabInstance: value: ConnectionModeButtons objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: d725b5588e1b956458798319e6541d84, type: 3} --- !u!224 &1865409449 stripped RectTransform: @@ -5478,7 +5525,6 @@ RectTransform: m_Children: - {fileID: 1549858059} m_Father: {fileID: 2058276876} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.25} m_AnchorMax: {x: 1, y: 0.75} @@ -5517,7 +5563,6 @@ RectTransform: - {fileID: 1398648428} - {fileID: 906714044} m_Father: {fileID: 1187680250} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -5616,7 +5661,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2058276876} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.25} m_AnchorMax: {x: 1, y: 0.75} @@ -5688,9 +5732,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2028091268} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!23 &2028091270 @@ -5710,6 +5762,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -5750,13 +5805,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2028091268} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0.49999994, z: 30.5} m_LocalScale: {x: 60, y: 3, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1332123092} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &2058276875 GameObject: @@ -5792,7 +5847,6 @@ RectTransform: - {fileID: 1387688805} - {fileID: 562991979} m_Father: {fileID: 290861172} - m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 1} m_AnchorMax: {x: 0.5, y: 1} @@ -5894,7 +5948,6 @@ RectTransform: m_Children: - {fileID: 441263735} m_Father: {fileID: 1456950069} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -5971,7 +6024,6 @@ RectTransform: m_Children: - {fileID: 348836179} m_Father: {fileID: 1362072618} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -6041,13 +6093,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2107482020} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 318.45444, y: 110.697815, z: 216.79077} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2107482022 MonoBehaviour: @@ -6061,6 +6113,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: cb5f3e55f5dd247129d8a4979b80ebbb, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ClientServerToggle: {fileID: 1588117327} m_TrackSceneEvents: 1 --- !u!114 &2107482023 @@ -6076,15 +6129,24 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: GlobalObjectIdHash: 3197939627 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!1001 &2848221156282925290 PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 290861172} m_Modifications: - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, @@ -6244,4 +6306,20 @@ PrefabInstance: objectReference: {fileID: 11400000, guid: c10d995498e0c514a853c3506031d3fb, type: 2} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 575203310} + - {fileID: 37242883} + - {fileID: 1024114720} + - {fileID: 167044834} + - {fileID: 290861172} + - {fileID: 1113539280} + - {fileID: 1332123092} + - {fileID: 2107482021} + - {fileID: 960545998} From edc8b78f8a4144c982bd81be91d1636f9bce3c69 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 12 Nov 2024 12:54:39 -0500 Subject: [PATCH 124/236] fix: Hide Debug Simulator for NGO 2.0+ (#3121) * fix: Hide Debug Simulator for NGO 2.0+ * Add PR number to CHANGELOG entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ .../Runtime/Transports/UTP/UnityTransport.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 22bef68281..ef0b056c56 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121) + ## [2.1.1] - 2024-10-18 ### Added diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 60e49d0479..d3962e0fa1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -405,6 +405,7 @@ public struct SimulatorParameters #if UTP_TRANSPORT_2_0_ABOVE [Obsolete("DebugSimulator is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)] + [HideInInspector] #endif public SimulatorParameters DebugSimulator = new SimulatorParameters { From d218b2992ddb98f2eb1a06b9085724ca33352865 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 12 Nov 2024 17:33:28 -0600 Subject: [PATCH 125/236] fix: alternate player spawning no players or networkclient player registration (#3122) * fix This resolves the issue where the player object was not being set properly if using alternate instantiate and spawn methods. * test Adjusted the ValidatePlayerObjects test to spawn players the 3 different ways and to validate the NetworkSpawnManager.PlayerObjects contains the newly spawned player on all instances and NetworkClient.PlayerObject is properly set on the owning client's NetworkManager. * update adding changelog entry * update adding the PR number to the changelog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 10 ++- .../NetworkClientAndPlayerObjectTests.cs | 82 ++++++++++++++++++- 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ef0b056c56..d662e6dc5c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122) - Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`. (#3113) - Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111) - Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 61442eb08a..910b1b55e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -751,7 +751,15 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ networkObject.IsPlayerObject = isPlayerObject; networkObject.transform.position = position; networkObject.transform.rotation = rotation; - networkObject.SpawnWithOwnership(ownerClientId, destroyWithScene); + // If spawning as a player, then invoke SpawnAsPlayerObject + if (isPlayerObject) + { + networkObject.SpawnAsPlayerObject(ownerClientId, destroyWithScene); + } + else // Otherwise just spawn with ownership + { + networkObject.SpawnWithOwnership(ownerClientId, destroyWithScene); + } return networkObject; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index d7890c202f..699b82d636 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -216,6 +216,21 @@ private bool ValidatePlayerObjectOnClients(NetworkManager clientToValidate) m_ErrorLogLevel2.Append($"[Client-{client.LocalClientId} Prefab Mismatch][Expected GlobalObjectIdHash: {expectedGlobalObjectIdHash} but was {remoteNetworkClient.PlayerObject.GlobalObjectIdHash}]"); success = false; } + + var foundPlayer = false; + foreach (var playerObject in client.SpawnManager.PlayerObjects) + { + if (playerObject.NetworkObjectId == clientToValidate.LocalClient.PlayerObject.NetworkObjectId) + { + foundPlayer = true; + break; + } + } + if (!foundPlayer) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {clientToValidate.LocalClient.PlayerObject.name}!"); + success = false; + } } return success; } @@ -231,6 +246,12 @@ private bool ValidateAllPlayerObjects() m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}]{m_ErrorLogLevel2}"); success = false; } + + if (m_ServerNetworkManager.LocalClient.PlayerObject == null) + { + m_ErrorLogLevel1.AppendLine($"[Client-{m_ServerNetworkManager.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!"); + success = false; + } } foreach (var client in m_ClientNetworkManagers) @@ -240,6 +261,17 @@ private bool ValidateAllPlayerObjects() m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}]{m_ErrorLogLevel2}"); success = false; } + if (client.LocalClient.PlayerObject == null) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!"); + success = false; + } + else + if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!"); + success = false; + } } return success; @@ -250,12 +282,19 @@ private NetworkObject GetRandomPlayerPrefab() return m_PlayerPrefabs[Random.Range(0, m_PlayerPrefabs.Count() - 1)].GetComponent(); } + public enum PlayerSpawnTypes + { + Normal, + NetworkObject, + SpawnManager + } + /// /// Validates that when a client changes their player object that all connected client instances mirror the /// client's new player object. /// [UnityTest] - public IEnumerator ValidatePlayerObjects() + public IEnumerator ValidatePlayerObjects([Values] PlayerSpawnTypes playerSpawnType) { // Just do a quick validation for all connected client's NetworkClients yield return WaitForConditionOrTimeOut(AllNetworkClientsValidated); @@ -268,14 +307,51 @@ public IEnumerator ValidatePlayerObjects() if (m_UseHost) { playerPrefabToSpawn = GetRandomPlayerPrefab(); - playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, m_ServerNetworkManager); + switch (playerSpawnType) + { + case PlayerSpawnTypes.Normal: + { + playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, m_ServerNetworkManager); + break; + } + case PlayerSpawnTypes.NetworkObject: + { + playerInstance = NetworkObject.InstantiateAndSpawn(playerPrefabToSpawn.gameObject, m_ServerNetworkManager, isPlayerObject: true).gameObject; + break; + } + case PlayerSpawnTypes.SpawnManager: + { + playerInstance = m_ServerNetworkManager.SpawnManager.InstantiateAndSpawn(playerPrefabToSpawn, isPlayerObject: true).gameObject; + break; + } + } + m_ChangedPlayerPrefabs.Add(m_ServerNetworkManager.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash); } foreach (var client in m_ClientNetworkManagers) { playerPrefabToSpawn = GetRandomPlayerPrefab(); - playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, client); + var networkManager = m_DistributedAuthority ? client : m_ServerNetworkManager; + + switch (playerSpawnType) + { + case PlayerSpawnTypes.Normal: + { + playerInstance = SpawnPlayerObject(playerPrefabToSpawn.gameObject, client); + break; + } + case PlayerSpawnTypes.NetworkObject: + { + playerInstance = NetworkObject.InstantiateAndSpawn(playerPrefabToSpawn.gameObject, networkManager, client.LocalClientId, isPlayerObject: true).gameObject; + break; + } + case PlayerSpawnTypes.SpawnManager: + { + playerInstance = networkManager.SpawnManager.InstantiateAndSpawn(playerPrefabToSpawn, client.LocalClientId, isPlayerObject: true).gameObject; + break; + } + } m_ChangedPlayerPrefabs.Add(client.LocalClientId, playerPrefabToSpawn.GlobalObjectIdHash); } From 5d97c457fa298efadc6f5ef0b54f15f5470736f9 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 14 Nov 2024 18:18:34 -0600 Subject: [PATCH 126/236] fix: in-sceneobject networkobject update in editor tool update (up-port-fix) (#3123) * fix Assign the current NetworkObject prefab to be updated. * style adding comment --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 3ccd8ea72e..74803aa428 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -106,6 +106,8 @@ public uint PrefabIdHash internal void RefreshAllPrefabInstances() { var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this); + // Assign the currently selected instance to be updated + NetworkObjectRefreshTool.PrefabNetworkObject = this; if (!PrefabUtility.IsPartOfAnyPrefab(this) || instanceGlobalId.identifierType != k_ImportedAssetObjectType) { EditorUtility.DisplayDialog("Network Prefab Assets Only", "This action can only be performed on a network prefab asset.", "Ok"); From 72e4f201226cf1fe7e15ea972630d4e3a74d708d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 18 Nov 2024 15:37:56 -0600 Subject: [PATCH 127/236] fix: NetworkVariable dispose original internal (#3128) * fix Assure we dispose the original internal value. * style adding some comments and formatting a bit. --- .../Runtime/NetworkVariable/NetworkVariable.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 16ca42ad91..a030884a09 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -181,19 +181,26 @@ public override void Dispose() } m_IsDisposed = true; + // Dispose the internal value if (m_InternalValue is IDisposable internalValueDisposable) { internalValueDisposable.Dispose(); } - m_InternalValue = default; + + // Dispose the internal original value + if (m_InternalOriginalValue is IDisposable internalOriginalValueDisposable) + { + internalOriginalValueDisposable.Dispose(); + } m_InternalOriginalValue = default; + + // Dispose the previous value if there is one if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable) { m_HasPreviousValue = false; previousValueDisposable.Dispose(); } - m_PreviousValue = default; base.Dispose(); From 90f97fd7ed118a3d72b16cdb4f81709c7436fad4 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 19 Nov 2024 15:22:03 -0600 Subject: [PATCH 128/236] fix: provide users ability to get client endpoint (#3130) * fix Adding GetEndpoint method so users can get endpoint connection info from an NGO client identifier. * test adding test to validate the GetEndpoint method addition. * test fixing issue with NetworkManagerTests not using the right property. * update adding change log entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 24 ++++++++++ .../Runtime/NetworkManagerTransportTests.cs | 48 +++++++++++++++++++ .../Tests/Runtime/NetworkManagerTests.cs | 5 +- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d662e6dc5c..8c4bb1122e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `UnityTransport.GetEndpoint` method to provide a way to obtain `NetworkEndpoint` information of a connection via client identifier. (#3130) - Added `NetworkTransport.OnEarlyUpdate` and `NetworkTransport.OnPostLateUpdate` methods to provide more control over handling transport related events at the start and end of each frame. (#3113) ### Fixed diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index d3962e0fa1..4a3edac16b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1262,6 +1262,30 @@ public override ulong GetCurrentRtt(ulong clientId) return (ulong)ExtractRtt(ParseClientId(clientId)); } + /// + /// Provides the for the NGO client identifier specified. + /// + /// + /// - This is only really useful for direct connections. + /// - Relay connections and clients connected using a distributed authority network topology will not provide the client's actual endpoint information. + /// - For LAN topologies this should work as long as it is a direct connection and not a relay connection. + /// + /// NGO client identifier to get endpoint information about. + /// + public NetworkEndpoint GetEndpoint(ulong clientId) + { + if (m_Driver.IsCreated && m_NetworkManager != null && m_NetworkManager.IsListening) + { + var transportId = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId); + var networkConnection = ParseClientId(transportId); + if (m_Driver.GetConnectionState(networkConnection) == NetworkConnection.State.Connected) + { + return m_Driver.GetRemoteEndpoint(networkConnection); + } + } + return new NetworkEndpoint(); + } + /// /// Initializes the transport /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs index 788758c90d..484f6301fd 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerTransportTests.cs @@ -1,6 +1,9 @@ using System; using System.Collections; using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.UTP; +using Unity.Networking.Transport; using UnityEngine; using UnityEngine.TestTools; @@ -157,4 +160,49 @@ public override void DisconnectLocalClient() } } } + + /// + /// Verifies the UnityTransport.GetEndpoint method returns + /// valid NetworkEndpoint information. + /// + internal class TransportEndpointTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + [UnityTest] + public IEnumerator GetEndpointReportedCorrectly() + { + var serverUnityTransport = m_ServerNetworkManager.NetworkConfig.NetworkTransport as UnityTransport; + var serverEndpoint = new NetworkEndpoint(); + var clientEndpoint = new NetworkEndpoint(); + foreach (var client in m_ClientNetworkManagers) + { + var unityTransport = client.NetworkConfig.NetworkTransport as UnityTransport; + serverEndpoint = unityTransport.GetEndpoint(m_ServerNetworkManager.LocalClientId); + clientEndpoint = serverUnityTransport.GetEndpoint(client.LocalClientId); + Assert.IsTrue(serverEndpoint.IsValid); + Assert.IsTrue(clientEndpoint.IsValid); + Assert.IsTrue(clientEndpoint.Address.Split(":")[0] == unityTransport.ConnectionData.Address); + Assert.IsTrue(serverEndpoint.Address.Split(":")[0] == serverUnityTransport.ConnectionData.Address); + Assert.IsTrue(serverEndpoint.Port == unityTransport.ConnectionData.Port); + Assert.IsTrue(clientEndpoint.Port >= serverUnityTransport.ConnectionData.Port); + } + + // Now validate that when disconnected it returns a non-valid NetworkEndPoint + var clientId = m_ClientNetworkManagers[0].LocalClientId; + m_ClientNetworkManagers[0].Shutdown(); + yield return s_DefaultWaitForTick; + + serverEndpoint = (m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport as UnityTransport).GetEndpoint(m_ServerNetworkManager.LocalClientId); + clientEndpoint = serverUnityTransport.GetEndpoint(clientId); + Assert.IsFalse(serverEndpoint.IsValid); + Assert.IsFalse(clientEndpoint.IsValid); + + // Validate that invalid client identifiers return an invalid NetworkEndPoint + serverEndpoint = (m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport as UnityTransport).GetEndpoint((ulong)UnityEngine.Random.Range(NumberOfClients + 1, 30)); + clientEndpoint = serverUnityTransport.GetEndpoint((ulong)UnityEngine.Random.Range(NumberOfClients + 1, 30)); + Assert.IsFalse(serverEndpoint.IsValid); + Assert.IsFalse(clientEndpoint.IsValid); + } + } } diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs index 277e8f8836..b09dd5e291 100644 --- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs @@ -22,7 +22,6 @@ public enum UseSceneManagement SceneManagementDisabled } - private bool m_EnableSceneManagement; private NetworkObject m_NetworkObject; private bool m_NetworkObjectWasSpawned; private bool m_NetworkBehaviourIsHostWasSet; @@ -85,7 +84,7 @@ protected override IEnumerator OnTearDown() protected override void OnServerAndClientsCreated() { - m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_UseSceneManagement; m_NetworkObjectTestComponent.ConfigureClientConnected(m_ServerNetworkManager, OnClientConnectedCallback); } @@ -108,7 +107,7 @@ public enum ShutdownChecks protected override void OnNewClientCreated(NetworkManager networkManager) { - networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + networkManager.NetworkConfig.EnableSceneManagement = m_UseSceneManagement; foreach (var prefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) { networkManager.NetworkConfig.Prefabs.Add(prefab); From 487e469c7c87eabd50ed0325005b08617c398e9c Mon Sep 17 00:00:00 2001 From: Dominick Date: Wed, 20 Nov 2024 15:04:31 -0700 Subject: [PATCH 129/236] feat: server distribution config (#3132) * feat: server distribution config * Update ConnectionApprovedMessage.cs --- .../Messaging/Messages/ConnectionApprovedMessage.cs | 13 +++++++++++++ .../Messaging/Messages/ConnectionRequestMessage.cs | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 7b9a87fe9c..ace61edac1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -5,9 +5,12 @@ namespace Unity.Netcode { internal struct ServiceConfig : INetworkSerializable { + private const int k_AddServerRedistribution = 1; + public uint Version; public bool IsRestoredSession; public ulong CurrentSessionOwner; + public bool ServerRedistribution; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { @@ -16,12 +19,22 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), Version); serializer.SerializeValue(ref IsRestoredSession); BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), CurrentSessionOwner); + + if (Version >= k_AddServerRedistribution) + { + serializer.SerializeValue(ref ServerRedistribution); + } } else { ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out Version); serializer.SerializeValue(ref IsRestoredSession); ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out CurrentSessionOwner); + + if (Version >= k_AddServerRedistribution) + { + serializer.SerializeValue(ref ServerRedistribution); + } } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index d8e60d2537..d5f19c2c00 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -12,7 +12,8 @@ internal struct ClientConfig : INetworkSerializable /// is not bypass feature compatible. ///
private const int k_BypassFeatureCompatible = 1; - public int Version => k_BypassFeatureCompatible; + private const int k_ServerDistributionCompatible = k_BypassFeatureCompatible + 1; + public int Version => k_ServerDistributionCompatible; public uint TickRate; public bool EnableSceneManagement; From 1845a33f1f27d84c1891b3b6115643f6cda20a53 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 21 Nov 2024 20:40:46 -0600 Subject: [PATCH 130/236] fix: synchronizing connected clients additively loaded scenes only when server (#3133) * fix This fixes the issue when starting NetworkManager as server only, connecting one or more clients, and then loading a scene and the in-scene placed NetworkObjects do not get synchronized with the clients (but once the scene is loaded late joining clients do get synchronized). This had to do with an adjustment to determining which in-scene placed NetworkObjects should be synchronized based on their observers coupled with both when running as just a server the server would not add itself to the observer list (in-scene placed only) and the update to NetworkSceneManager.SendSceneEventData where it never was updated with the more recent changes to SceneEventData (i.e. only serializing the in-scene placed NetworkObjects a client is observing) and still just queueing the message to be sent with a full list of target ids as opposed to sending each individual message while setting the SceneEventData.TargetClientId. * test Updating some of the scene management related integration tests to validate the issue associated with this PR as well as adding a server (non-host) set to some of the integration tests that were only validating with a host. * style adding comment and removing extra CR/LF. * update adding changelog entries * test - fix Fixing issue with the updated InScenePlacedNetworkObjectTests that were using the server-side player for some of the tests which doesn't exist when it is just a server. Fixing issue with the expected number of spawned objects since the base value was derived from TotalClients that changes when running a server. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../SceneManagement/NetworkSceneManager.cs | 17 +++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 6 ++ .../IntegrationTestWithApproximation.cs | 2 + .../Runtime/NetcodeIntegrationTest.cs | 7 ++ .../ClientSynchronizationValidationTest.cs | 45 ++++++++++-- .../InScenePlacedNetworkObjectTests.cs | 71 ++++++++++++------- ...NetworkSceneManagerPopulateInSceneTests.cs | 7 +- 8 files changed, 116 insertions(+), 41 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8c4bb1122e..9f2f95e72e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133) +- Fixed issue where a `NetworkManager` started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133) - Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122) - Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`. (#3113) - Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 65222007bf..acffc411f8 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1081,14 +1081,19 @@ private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds) } else { - var message = new SceneEventMessage + // Send to each individual client to assure only the in-scene placed NetworkObjects being observed by the client + // is serialized + foreach (var clientId in targetClientIds) { - EventData = sceneEvent, - }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, targetClientIds); - NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size); + sceneEvent.TargetClientId = clientId; + var message = new SceneEventMessage + { + EventData = sceneEvent, + }; + var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, clientId); + NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEvent.SceneEventType, SceneNameFromHash(sceneEvent.SceneHash), size); + } } - } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 910b1b55e1..8bc3517bae 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1119,6 +1119,12 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong // then add all connected clients as observers if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkObject.SpawnWithObservers) { + // If running as a server only, then make sure to always add the server's client identifier + if (!NetworkManager.IsHost) + { + networkObject.Observers.Add(NetworkManager.LocalClientId); + } + // Add client observers for (int i = 0; i < NetworkManager.ConnectedClientsIds.Count; i++) { diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs index 02a5393217..89b1e89e53 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs @@ -87,6 +87,8 @@ protected Vector3 GetRandomVector3(float min, float max) return new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(min, max)); } + public IntegrationTestWithApproximation(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + public IntegrationTestWithApproximation(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } public IntegrationTestWithApproximation(HostOrServer hostOrServer) : base(hostOrServer) { } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index a84a7ccb7c..921d70b928 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -1759,6 +1759,13 @@ public NetcodeIntegrationTest(NetworkTopologyTypes networkTopologyType) m_DistributedAuthority = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority; } + public NetcodeIntegrationTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) + { + m_NetworkTopologyType = networkTopologyType; + m_DistributedAuthority = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority; + m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost; + } + /// /// Optional Host or Server integration tests /// Constructor that allows you To break tests up as a host diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs index 31f54ce5de..99781be873 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs @@ -9,8 +9,9 @@ namespace TestProject.RuntimeTests { - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] - [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] public class ClientSynchronizationValidationTest : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -22,7 +23,7 @@ public class ClientSynchronizationValidationTest : NetcodeIntegrationTest private bool m_RuntimeSceneWasExcludedFromSynch; private List m_ClientSceneVerifiers = new List(); - public ClientSynchronizationValidationTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + public ClientSynchronizationValidationTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } protected override void OnNewClientStarted(NetworkManager networkManager) { @@ -78,11 +79,17 @@ public IEnumerator ClientSynchWithServerSideRuntimeGeneratedScene() /// Validates that connecting clients will exclude scenes using /// [UnityTest] - public IEnumerator ClientVerifySceneBeforeLoading() + public IEnumerator ClientVerifySceneBeforeLoading([Values] bool startClientBefore) { m_IncludeSceneVerificationHandler = true; var scenesToLoad = new List() { k_FirstSceneToLoad, k_SecondSceneToLoad, k_ThirdSceneToSkip }; m_ServerNetworkManager.SceneManager.OnLoadComplete += OnLoadComplete; + + if (startClientBefore) + { + yield return CreateAndStartNewClient(); + } + foreach (var sceneToLoad in scenesToLoad) { m_SceneBeingLoadedIsLoaded = false; @@ -91,9 +98,25 @@ public IEnumerator ClientVerifySceneBeforeLoading() yield return WaitForConditionOrTimeOut(() => m_SceneBeingLoadedIsLoaded); AssertOnTimeout($"Timed out waiting for scene {m_SceneBeingLoaded} to finish loading!"); + + var serverId = m_ServerNetworkManager.LocalClientId; + var serverOrHost = m_ServerNetworkManager.IsHost ? "Host" : "Server"; + foreach (var spawnedObjectEntry in m_ServerNetworkManager.SpawnManager.SpawnedObjects) + { + var networkObject = spawnedObjectEntry.Value; + if (!networkObject.IsSceneObject.Value) + { + continue; + } + + Assert.True(networkObject.Observers.Contains(serverId), $"The {serverOrHost} is not an observer of in-scene placed {nameof(NetworkObject)} {networkObject.name}!"); + } } - yield return CreateAndStartNewClient(); + if (!startClientBefore) + { + yield return CreateAndStartNewClient(); + } yield return WaitForConditionOrTimeOut(m_ClientSceneVerifiers[0].HasLoadedExpectedScenes); AssertOnTimeout($"Timed out waiting for the client to have loaded the expected scenes"); @@ -104,6 +127,18 @@ public IEnumerator ClientVerifySceneBeforeLoading() { clientSceneVerifier.ValidateScenesLoaded(); } + + // Finally, validate that all in-scene placed NetworkObjects were properly synchronized/spawned on the client side + foreach (var spawnedObjectEntry in m_ServerNetworkManager.SpawnManager.SpawnedObjects) + { + var networkObject = spawnedObjectEntry.Value; + if (!networkObject.IsSceneObject.Value) + { + continue; + } + Assert.True(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId), $"{nameof(NetworkObject)}-{networkObject.NetworkObjectId} " + + $"did not synchronize on Client-{m_ClientNetworkManagers[0].LocalClientId}!"); + } } private string m_SceneBeingLoaded; diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index 446daea85a..59922c5959 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -13,11 +13,12 @@ namespace TestProject.RuntimeTests { - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] - [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] public class InScenePlacedNetworkObjectTests : IntegrationTestWithApproximation { - protected override int NumberOfClients => 2; + protected override int NumberOfClients => 3; private const string k_SceneToLoad = "InSceneNetworkObject"; private const string k_InSceneUnder = "InSceneUnderGameObject"; @@ -26,7 +27,7 @@ public class InScenePlacedNetworkObjectTests : IntegrationTestWithApproximation private bool m_CanStartServerAndClients; private string m_SceneLoading = k_SceneToLoad; - public InScenePlacedNetworkObjectTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + public InScenePlacedNetworkObjectTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } protected override IEnumerator OnSetup() { @@ -62,6 +63,7 @@ protected override bool CanStartServerAndClients() [UnityTest] public IEnumerator InSceneNetworkObjectSynchAndSpawn() { + NetworkObjectTestComponent.VerboseDebug = true; // Because despawning a client will cause it to shutdown and clean everything in the // scene hierarchy, we have to prevent one of the clients from spawning initially before // we test synchronizing late joining clients with despawned in-scene placed NetworkObjects. @@ -69,23 +71,25 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn() // will be targeting to join late from the m_ClientNetworkManagers array, start the server // and the remaining client, despawn the in-scene NetworkObject, and then start and synchronize // the clientToTest. - var clientToTest = m_ClientNetworkManagers[1]; + var clientToTest = m_ClientNetworkManagers[2]; var clients = m_ClientNetworkManagers.ToList(); clients.Remove(clientToTest); m_ClientNetworkManagers = clients.ToArray(); m_CanStartServerAndClients = true; + NetworkObjectTestComponent.Reset(); yield return StartServerAndClients(); clients.Add(clientToTest); m_ClientNetworkManagers = clients.ToArray(); - NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + m_ServerNetworkManager.SceneManager.OnSceneEvent += Server_OnSceneEvent; var status = m_ServerNetworkManager.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}"); - + // We removed a client from the initial spawn and server should spawn too + var clientCount = TotalClients - (m_UseHost ? 1 : 0); // This verifies the scene loaded and the in-scene placed NetworkObjects spawned. - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients - 1); - AssertOnTimeout($"Timed out waiting for total spawned in-scene placed NetworkObjects to reach a count of {TotalClients - 1} and is currently {NetworkObjectTestComponent.SpawnedInstances.Count}"); + 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}"); // Get the server-side instance of the in-scene NetworkObject Assert.True(s_GlobalNetworkObjects.ContainsKey(m_ServerNetworkManager.LocalClientId), $"Could not find server instance of the test in-scene NetworkObject!"); @@ -102,6 +106,9 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn() // Now late join a client NetworkObjectTestComponent.OnInSceneObjectDespawned += OnInSceneObjectDespawned; NetcodeIntegrationTestHelpers.StartOneClient(clientToTest); + // Spawned another client + clientCount++; + yield return WaitForConditionOrTimeOut(() => (clientToTest.IsConnectedClient && clientToTest.IsListening)); AssertOnTimeout($"Timed out waiting for {clientToTest.name} to reconnect!"); @@ -120,21 +127,24 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn() // Now test that the despawned in-scene placed NetworkObject can be re-spawned (without having been registered as a NetworkPrefab) serverObject.Spawn(); - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); - AssertOnTimeout($"Timed out waiting for all in-scene instances to be spawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); + 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 = m_ClientNetworkManagers[0].LocalClientId; serverObject.NetworkHide(firstClientId); + clientCount--; - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients - 1); - AssertOnTimeout($"[NetworkHide] Timed out waiting for Client-{firstClientId} to despawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients - 1}"); + 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); - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); - AssertOnTimeout($"[NetworkShow] Timed out waiting for Client-{firstClientId} to spawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); + 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}"); CleanUpLoadedScene(); } @@ -151,7 +161,7 @@ public IEnumerator ParentedInSceneObjectLateJoiningClient() // will be targeting to join late from the m_ClientNetworkManagers array, start the server // and the remaining client, despawn the in-scene NetworkObject, and then start and synchronize // the clientToTest. - var clientToTest = m_ClientNetworkManagers[1]; + var clientToTest = m_ClientNetworkManagers[2]; var clients = m_ClientNetworkManagers.ToList(); clients.Remove(clientToTest); m_ClientNetworkManagers = clients.ToArray(); @@ -173,12 +183,22 @@ public IEnumerator ParentedInSceneObjectLateJoiningClient() Assert.IsNotNull(firstClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)}!"); Assert.IsTrue(firstClientInSceneObjectInstance.NetworkManager == m_ClientNetworkManagers[0]); - // Parent the object - serverInSceneObjectInstance.transform.parent = m_ServerNetworkManager.LocalClient.PlayerObject.transform; + var playerObjectToParent = m_UseHost ? m_ServerNetworkManager.LocalClient.PlayerObject : m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[1].LocalClientId]; + var clientSidePlayer = (NetworkObject)null; + if (!m_UseHost) + { + playerObjectToParent = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[1].LocalClientId]; + clientSidePlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[1].LocalClientId]; + } + else + { + clientSidePlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][NetworkManager.ServerClientId]; + } - var clientSideServerPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][NetworkManager.ServerClientId]; + // Parent the object + serverInSceneObjectInstance.transform.parent = playerObjectToParent.transform; - yield return WaitForConditionOrTimeOut(() => firstClientInSceneObjectInstance.transform.parent != null && firstClientInSceneObjectInstance.transform.parent == clientSideServerPlayer.transform); + yield return WaitForConditionOrTimeOut(() => firstClientInSceneObjectInstance.transform.parent != null && firstClientInSceneObjectInstance.transform.parent == clientSidePlayer.transform); AssertOnTimeout($"Timed out waiting for the client-side id ({m_ClientNetworkManagers[0].LocalClientId}) server player transform to be set on the client-side in-scene object!"); // Now late join a client NetcodeIntegrationTestHelpers.StartOneClient(clientToTest); @@ -190,15 +210,15 @@ public IEnumerator ParentedInSceneObjectLateJoiningClient() // Update the newly joined client information ClientNetworkManagerPostStartInit(); - var lateJoinClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.Where((c) => c.NetworkManager == m_ClientNetworkManagers[1]).FirstOrDefault(); + var lateJoinClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.Where((c) => c.NetworkManager == m_ClientNetworkManagers[2]).FirstOrDefault(); 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 - clientSideServerPlayer = m_PlayerNetworkObjects[clientToTest.LocalClientId][NetworkManager.ServerClientId]; + clientSidePlayer = m_PlayerNetworkObjects[clientToTest.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 == clientSideServerPlayer.transform); - AssertOnTimeout($"Timed out waiting for the client-side id ({m_ClientNetworkManagers[0].LocalClientId}) server player transform to be set on the client-side in-scene object!"); + yield return WaitForConditionOrTimeOut(() => lateJoinClientInSceneObjectInstance.transform.parent != null && lateJoinClientInSceneObjectInstance.transform.parent == clientSidePlayer.transform); + AssertOnTimeout($"Timed out waiting for the client-side id ({m_ClientNetworkManagers[0].LocalClientId}) player transform to be set on the client-side in-scene object!"); } private void OnSceneEvent(SceneEvent sceneEvent) @@ -484,9 +504,6 @@ public IEnumerator ParentedInSceneObjectUnderGameObject([Values(k_InSceneUnder, Assert.IsNotNull(firstClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)}!"); Assert.IsTrue(firstClientInSceneObjectInstance.NetworkManager == m_ClientNetworkManagers[0]); - // Parent the object - var clientSideServerPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][NetworkManager.ServerClientId]; - serverInSceneObjectInstance.AutoObjectParentSync = parentSyncSettings == ParentSyncSettings.ParentSync; serverInSceneObjectInstance.SynchronizeTransform = transformSyncSettings == TransformSyncSettings.TransformSync; diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs index 7f344584ea..4af8737c44 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs @@ -11,8 +11,9 @@ namespace TestProject.RuntimeTests { - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] - [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -20,7 +21,7 @@ public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest protected Dictionary m_InSceneObjectList = new Dictionary(); - public NetworkSceneManagerPopulateInSceneTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + public NetworkSceneManagerPopulateInSceneTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } protected override IEnumerator OnSetup() { From 86ddd7d3e6e047fd264a7c8de6b4acb53afdfc44 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 22 Nov 2024 19:23:53 -0600 Subject: [PATCH 131/236] chore: object synchronization distributed authority mode when scene management disabled (#3135) * fix This fixes most of the synchronization issues with using a distributed authority network topology and scene management is disabled. (There still is some form of issue with changing ownership when distributing objects) * update A better way to handle object redistribution and NetworkObject synchronization when scene management is disabled, --- .../Connection/NetworkConnectionManager.cs | 8 +++ .../Runtime/Core/NetworkBehaviourUpdater.cs | 3 ++ .../Runtime/Core/NetworkManager.cs | 38 +++++++++----- .../Runtime/Core/NetworkObject.cs | 10 ++-- .../Messages/ChangeOwnershipMessage.cs | 4 +- .../Messages/ClientConnectedMessage.cs | 21 +++++--- .../Runtime/Spawning/NetworkSpawnManager.cs | 52 ++++++++++++++++--- 7 files changed, 101 insertions(+), 35 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 5c26df4b91..81444f8482 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1001,6 +1001,14 @@ internal NetworkClient AddClient(ulong clientId) } var distributedAuthority = NetworkManager.DistributedAuthorityMode; + + // If not using DA return early or if using DA and scene management is disabled then exit early Since we use NetworkShow to spawn + // objects on the newly connected client side. + if (!distributedAuthority || distributedAuthority && !NetworkManager.NetworkConfig.EnableSceneManagement) + { + return networkClient; + } + var sessionOwnerId = NetworkManager.CurrentSessionOwner; var isSessionOwner = NetworkManager.LocalClient.IsSessionOwner; foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 9062ebf113..6a1dd748e6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -141,6 +141,9 @@ private void NetworkBehaviourUpdater_Tick() // Then show any NetworkObjects queued to be made visible/shown m_NetworkManager.SpawnManager.HandleNetworkObjectShow(); + + // Handle object redistribution (DA + disabled scene management only) + m_NetworkManager.HandleRedistributionToClients(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 56a82bc33f..26be7c9266 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -162,10 +162,30 @@ public bool DAHost } } - // DANGO-TODO-MVP: Remove these properties once the service handles object distribution - internal ulong ClientToRedistribute; - internal bool RedistributeToClient; - internal int TickToRedistribute; + // DANGO-TODO: Determine if this needs to be removed once the service handles object distribution + internal List ClientsToRedistribute = new List(); + internal bool RedistributeToClients; + + /// + /// Handles object redistribution when scene management is disabled. + /// + /// DANGO-TODO: Determine if this needs to be removed once the service handles object distribution + /// + internal void HandleRedistributionToClients() + { + if (!DistributedAuthorityMode || !RedistributeToClients || NetworkConfig.EnableSceneManagement || ShutdownInProgress) + { + return; + } + + foreach (var clientId in ClientsToRedistribute) + { + SpawnManager.DistributeNetworkObjects(clientId); + } + RedistributeToClients = false; + ClientsToRedistribute.Clear(); + } + internal List DeferredDespawnObjects = new List(); @@ -393,16 +413,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period. DeferredMessageManager.CleanupStaleTriggers(); - // DANGO-TODO-MVP: Remove this once the service handles object distribution - // NOTE: This needs to be the last thing done and should happen exactly at this point - // in the update - if (RedistributeToClient && ServerTime.Tick <= TickToRedistribute) - { - RedistributeToClient = false; - SpawnManager.DistributeNetworkObjects(ClientToRedistribute); - ClientToRedistribute = 0; - } - if (m_ShuttingDown) { // Host-server will disconnect any connected clients prior to finalizing its shutdown diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 74803aa428..661c1ce41b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2894,7 +2894,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager SyncObservers = syncObservers, Observers = syncObservers ? Observers.ToArray() : null, NetworkSceneHandle = NetworkSceneHandle, - Hash = HostCheckForGlobalObjectIdHashOverride(), + Hash = CheckForGlobalObjectIdHashOverride(), OwnerObject = this, TargetClientId = targetClientId }; @@ -3246,14 +3246,14 @@ internal void UpdateForSceneChanges() } /// - /// Only applies to Host mode. + /// Only applies to Hosts or session owners (for now) /// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists. /// Server and Clients will always return the NetworkObject's GlobalObjectIdHash. /// - /// - internal uint HostCheckForGlobalObjectIdHashOverride() + /// appropriate hash value + internal uint CheckForGlobalObjectIdHashOverride() { - if (NetworkManager.IsServer) + if (NetworkManager.IsServer || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner)) { if (NetworkManager.PrefabHandler.ContainsHandler(this)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index d418789f4d..cb4f114b91 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -332,8 +332,8 @@ private void HandleOwnershipChange(ref NetworkContext context) // Sanity check that we are not sending duplicated change ownership messages if (networkObject.OwnerClientId == OwnerClientId) { - UnityEngine.Debug.LogError($"Unnecessary ownership changed message for {NetworkObjectId}."); - // Ignore the message + // Log error and then ignore the message + UnityEngine.Debug.LogError($"Client-{context.SenderId} ({RequestClientId}) sent unnecessary ownership changed message for {NetworkObjectId}."); return; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index ef7ef5f9a1..0234bc8b67 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -55,19 +55,24 @@ public void Handle(ref NetworkContext context) // Don't redistribute for the local instance if (ClientId != networkManager.LocalClientId) { + // Synchronize the client with spawned objects (relative to each client) + networkManager.SpawnManager.SynchronizeObjectsToNewlyJoinedClient(ClientId); + + // Keeping for reference in case the above doesn't resolve for hidden objects (theoretically it should) // Show any NetworkObjects that are: // - Hidden from the session owner // - Owned by this client // - Has NetworkObject.SpawnWithObservers set to true (the default) - if (!networkManager.LocalClient.IsSessionOwner) - { - networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId); - } + //if (!networkManager.LocalClient.IsSessionOwner) + //{ + // networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId); + //} - // We defer redistribution to the end of the NetworkUpdateStage.PostLateUpdate - networkManager.RedistributeToClient = true; - networkManager.ClientToRedistribute = ClientId; - networkManager.TickToRedistribute = networkManager.ServerTime.Tick + 20; + /// We defer redistribution to happen after NetworkShow has been invoked + /// + /// DANGO-TODO: Determine if this needs to be removed once the service handles object distribution + networkManager.RedistributeToClients = true; + networkManager.ClientsToRedistribute.Add(ClientId); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 8bc3517bae..571fe4736e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -580,7 +580,6 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); } - size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ServerClientId); NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(NetworkManager.LocalClientId, networkObject, size); } @@ -1982,14 +1981,14 @@ internal void NotifyNetworkObjectsSynchronized() /// synchronizing in order to "show" (spawn) anything that might be currently hidden from /// the session owner. /// + /// + /// Replacement is: SynchronizeObjectsToNewlyJoinedClient + /// internal void ShowHiddenObjectsToNewlyJoinedClient(ulong newClientId) { - if (!NetworkManager.DistributedAuthorityMode) + if (NetworkManager == null || NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer) { - if (NetworkManager == null || !NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer) - { - Debug.LogWarning($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} invoked while !"); - } + Debug.LogWarning($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} invoked while shutdown is in progress!"); return; } @@ -2025,5 +2024,46 @@ internal void ShowHiddenObjectsToNewlyJoinedClient(ulong newClientId) } } } + + internal void SynchronizeObjectsToNewlyJoinedClient(ulong newClientId) + { + if (NetworkManager == null || NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"[Internal Error] {nameof(SynchronizeObjectsToNewlyJoinedClient)} invoked while shutdown is in progress!"); + return; + } + + if (!NetworkManager.DistributedAuthorityMode) + { + Debug.LogError($"[Internal Error] {nameof(SynchronizeObjectsToNewlyJoinedClient)} should only be invoked when using a distributed authority network topology!"); + return; + } + + if (NetworkManager.NetworkConfig.EnableSceneManagement) + { + Debug.LogError($"[Internal Error] {nameof(SynchronizeObjectsToNewlyJoinedClient)} should only be invoked when scene management is disabled!"); + return; + } + + var localClientId = NetworkManager.LocalClient.ClientId; + foreach (var networkObject in SpawnedObjectsList) + { + if (networkObject.SpawnWithObservers && networkObject.OwnerClientId == localClientId) + { + if (networkObject.Observers.Contains(newClientId)) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + // Temporary tracking to make sure we are not showing something already visibile (should never be the case for this) + Debug.LogWarning($"[{nameof(SynchronizeObjectsToNewlyJoinedClient)}][{networkObject.name}] New client as already an observer!"); + } + // For now, remove the client (impossible for the new client to have an instance since the session owner doesn't) to make sure newly added + // code to handle this edge case works. + networkObject.Observers.Remove(newClientId); + } + networkObject.NetworkShow(newClientId); + } + } + } } } From 2b0a9b2888f43e1504da7c282d2e10c81627ee5e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 23 Nov 2024 22:07:07 -0600 Subject: [PATCH 132/236] fix: Server only session issues with parent synchronize and networkobjecteditor (#3139) * fix Fixed issue where NetworkObjectEditor would throw an exception when you tried to view the NetworkObject.Observers in the inspector view when in playmode. * fix Fixed issue where the parent sync message was ignoring deserialization on the server side (clients can parent if the NetworkObject has the allow clients to parent flag set). --- .../Editor/NetworkObjectEditor.cs | 8 ++++++++ .../Runtime/Messaging/Messages/ParentSyncMessage.cs | 4 ---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index ef6ea58c34..bd18473b27 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -88,6 +88,14 @@ public override void OnInspectorGUI() while (observerClientIds.MoveNext()) { + if (!m_NetworkObject.NetworkManager.ConnectedClients.ContainsKey(observerClientIds.Current)) + { + if ((observerClientIds.Current == 0 && m_NetworkObject.NetworkManager.IsHost) || observerClientIds.Current > 0) + { + Debug.LogWarning($"Client-{observerClientIds.Current} is listed as an observer but is not connected!"); + } + continue; + } if (m_NetworkObject.NetworkManager.ConnectedClients[observerClientIds.Current].PlayerObject != null) { EditorGUILayout.ObjectField($"ClientId: {observerClientIds.Current}", m_NetworkObject.NetworkManager.ConnectedClients[observerClientIds.Current].PlayerObject, typeof(GameObject), false); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index bef5fe8123..33caa7d114 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -70,10 +70,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { var networkManager = (NetworkManager)context.SystemOwner; - if (!networkManager.IsClient) - { - return false; - } ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); reader.ReadValueSafe(out m_BitField); From 17f7f259d76141a5d2852c8b05109053ef6c95e2 Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 26 Nov 2024 17:49:01 -0500 Subject: [PATCH 133/236] fix: ObjectDisposedException on NetworkManager after shutting down the Transport (#3142) fix: ObjectDisposedException thrown when shutting down the NetworkManager after shutting down the Transport --- .../Runtime/Transports/UTP/UnityTransport.cs | 6 +++ .../Transports/UnityTransportTestHelpers.cs | 18 ++++++++ .../Runtime/Transports/UnityTransportTests.cs | 41 +++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 4a3edac16b..634d544c84 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1495,6 +1495,11 @@ public override bool StartServer() ///
public override void Shutdown() { + if (m_NetworkManager && !m_NetworkManager.ShutdownInProgress) + { + Debug.LogWarning("Directly calling `UnityTransport.Shutdown()` results in unexpected shutdown behaviour. All pending events will be lost. Use `NetworkManager.Shutdown()` instead."); + } + if (m_Driver.IsCreated) { while (ProcessEvent() && m_Driver.IsCreated) @@ -1519,6 +1524,7 @@ public override void Shutdown() DisposeInternals(); m_ReliableReceiveQueues.Clear(); + m_State = State.Disconnected; // We must reset this to zero because UTP actually re-uses clientIds if there is a clean disconnect m_ServerClientId = 0; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs index 2523dd9257..926b2fa7bc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTestHelpers.cs @@ -36,6 +36,24 @@ public static IEnumerator WaitForNetworkEvent(NetworkEvent type, List events, float timeout = MaxNetworkEventWaitTime) + { + int initialCount = events.Count; + float startTime = Time.realtimeSinceStartup; + + while (Time.realtimeSinceStartup - startTime < timeout) + { + if (events.Count > initialCount) + { + Assert.Fail("Received unexpected network event."); + } + + yield return new WaitForSeconds(0.01f); + } + } + + // Common code to initialize a UnityTransport that logs its events. public static void InitializeTransport(out UnityTransport transport, out List events, int maxPayloadSize = UnityTransport.InitialMaxPayloadSize, int maxSendQueueSize = 0, NetworkFamily family = NetworkFamily.Ipv4) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index d5b720dada..016d45746c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -499,5 +499,46 @@ public IEnumerator ReliablePayloadsCanBeLargerThanMaximum() yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events); } + + public enum AfterShutdownAction + { + Send, + DisconnectRemoteClient, + DisconnectLocalClient, + } + + [UnityTest] + public IEnumerator DoesNotActAfterShutdown([Values] AfterShutdownAction afterShutdownAction) + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); + + m_Server.Shutdown(); + + if (afterShutdownAction == AfterShutdownAction.Send) + { + var data = new ArraySegment(new byte[16]); + m_Server.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable); + + yield return EnsureNoNetworkEvent(m_Client1Events); + } + else if (afterShutdownAction == AfterShutdownAction.DisconnectRemoteClient) + { + m_Server.DisconnectRemoteClient(m_Client1.ServerClientId); + + LogAssert.Expect(LogType.Assert, "DisconnectRemoteClient should be called on a listening server"); + } + else if (afterShutdownAction == AfterShutdownAction.DisconnectLocalClient) + { + m_Server.DisconnectLocalClient(); + + yield return EnsureNoNetworkEvent(m_Client1Events); + } + } } } From dfabbed35d3cd7d422e752de960e23c091ffa111 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 2 Dec 2024 15:20:17 -0600 Subject: [PATCH 134/236] chore: refactor session version serialization and configuration (#3138) * update Re-organizing how we handle session versioning and how service features are determined during a connected session. * update Updates to ConnectionRequestMessage that implement the new SessionConfig serialization pattern. * update Minor adjustments and clean up to the SessionConfig and SessionVersion implementation. * test Validation test for the SessionConfig and SessionVersion check (mocking the service-side handling logic for invalid session version). --- .../Runtime/Configuration/SessionConfig.cs | 58 ++++++ .../Configuration/SessionConfig.cs.meta | 2 + .../Connection/NetworkConnectionManager.cs | 5 +- .../Runtime/Core/NetworkManager.cs | 18 ++ .../Messages/ConnectionApprovedMessage.cs | 18 +- .../Messages/ConnectionRequestMessage.cs | 38 ++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 177 +++++++++--------- .../SessionVersionConnectionRequest.cs | 106 +++++++++++ .../SessionVersionConnectionRequest.cs.meta | 2 + 9 files changed, 318 insertions(+), 106 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs new file mode 100644 index 0000000000..671ae74811 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs @@ -0,0 +1,58 @@ +namespace Unity.Netcode +{ + internal class SessionConfig + { + /// + /// The running list of session versions + /// + public const uint NoFeatureCompatibility = 0; + public const uint BypassFeatureCompatible = 1; + public const uint ServerDistributionCompatible = 2; + + // The most current session version (!!!!set this when you increment!!!!!) + public static uint PackageSessionVersion => ServerDistributionCompatible; + + internal uint SessionVersion; + + public bool ServiceSideDistribution; + + + /// + /// Service to client + /// Set when the client receives a + /// + /// the session's settings + public SessionConfig(ServiceConfig serviceConfig) + { + SessionVersion = serviceConfig.SessionVersion; + ServiceSideDistribution = serviceConfig.ServerRedistribution; + } + + /// + /// Can be used to directly set the version. + /// + /// + /// If a client connects that does not support session configuration then + /// this will be invoked. The default values set in the constructor should + /// assume that no features are available. + /// Can also be used for mock/integration testing version handling. + /// + /// version to set + public SessionConfig(uint version) + { + SessionVersion = version; + ServiceSideDistribution = false; + } + + /// + /// Client to Service + /// Default package constructor set when is invoked. + /// + public SessionConfig() + { + // The current + SessionVersion = PackageSessionVersion; + ServiceSideDistribution = false; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs.meta b/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs.meta new file mode 100644 index 0000000000..bb361b9657 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e0512e5a3e1dc484bbbf98c03a574645 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 81444f8482..4296a6835a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -563,7 +563,7 @@ private void SendConnectionRequest() { var message = new ConnectionRequestMessage { - CMBServiceConnection = NetworkManager.CMBServiceConnection, + DistributedAuthority = NetworkManager.DistributedAuthorityMode, // Since only a remote client will send a connection request, we should always force the rebuilding of the NetworkConfig hash value ConfigHash = NetworkManager.NetworkConfig.GetConfig(false), ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval, @@ -571,8 +571,9 @@ private void SendConnectionRequest() MessageVersions = new NativeArray(MessageManager.MessageHandlers.Length, Allocator.Temp) }; - if (NetworkManager.CMBServiceConnection) + if (NetworkManager.DistributedAuthorityMode) { + message.ClientConfig.SessionConfig = NetworkManager.SessionConfig; message.ClientConfig.TickRate = NetworkManager.NetworkConfig.TickRate; message.ClientConfig.EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 26be7c9266..eacda7e87d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -80,6 +80,18 @@ internal static void LogSerializedTypeNotOptimized() } #endif + internal SessionConfig SessionConfig; + + /// + /// Used for internal testing purposes + /// + internal delegate SessionConfig OnGetSessionConfigHandler(); + internal OnGetSessionConfigHandler OnGetSessionConfig; + private SessionConfig GetSessionConfig() + { + return OnGetSessionConfig != null ? OnGetSessionConfig.Invoke() : new SessionConfig(); + } + internal static bool IsDistributedAuthority; /// @@ -1179,6 +1191,12 @@ internal void Initialize(bool server) UpdateTopology(); + // Always create a default session config when starting a NetworkManager instance + if (DistributedAuthorityMode) + { + SessionConfig = GetSessionConfig(); + } + // Make sure the ServerShutdownState is reset when initializing if (server) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index ace61edac1..5005c1987c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -5,9 +5,7 @@ namespace Unity.Netcode { internal struct ServiceConfig : INetworkSerializable { - private const int k_AddServerRedistribution = 1; - - public uint Version; + public uint SessionVersion; public bool IsRestoredSession; public ulong CurrentSessionOwner; public bool ServerRedistribution; @@ -16,25 +14,29 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { if (serializer.IsWriter) { - BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), Version); + BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), SessionVersion); serializer.SerializeValue(ref IsRestoredSession); BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), CurrentSessionOwner); - if (Version >= k_AddServerRedistribution) + if (SessionVersion >= SessionConfig.ServerDistributionCompatible) { serializer.SerializeValue(ref ServerRedistribution); } } else { - ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out Version); + ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out SessionVersion); serializer.SerializeValue(ref IsRestoredSession); ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out CurrentSessionOwner); - if (Version >= k_AddServerRedistribution) + if (SessionVersion >= SessionConfig.ServerDistributionCompatible) { serializer.SerializeValue(ref ServerRedistribution); } + else + { + ServerRedistribution = false; + } } } } @@ -203,11 +205,13 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int if (receivedMessageVersion >= k_AddCMBServiceConfig) { reader.ReadNetworkSerializable(out ServiceConfig); + networkManager.SessionConfig = new SessionConfig(ServiceConfig); } else { reader.ReadValueSafe(out IsRestoredSession); ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner); + networkManager.SessionConfig = new SessionConfig(SessionConfig.NoFeatureCompatibility); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index d5f19c2c00..4674b32961 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -7,32 +7,28 @@ namespace Unity.Netcode /// internal struct ClientConfig : INetworkSerializable { - /// - /// We start at version 1, where anything less than version 1 on the service side - /// is not bypass feature compatible. - /// - private const int k_BypassFeatureCompatible = 1; - private const int k_ServerDistributionCompatible = k_BypassFeatureCompatible + 1; - public int Version => k_ServerDistributionCompatible; + public SessionConfig SessionConfig; + public uint SessionVersion => SessionConfig.SessionVersion; public uint TickRate; public bool EnableSceneManagement; // Only gets deserialized but should never be used unless testing - public int RemoteClientVersion; + public uint RemoteClientSessionVersion; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { + // Clients always write if (serializer.IsWriter) { var writer = serializer.GetFastBufferWriter(); - BytePacker.WriteValueBitPacked(writer, Version); + BytePacker.WriteValueBitPacked(writer, SessionVersion); BytePacker.WriteValueBitPacked(writer, TickRate); writer.WriteValueSafe(EnableSceneManagement); } else { var reader = serializer.GetFastBufferReader(); - ByteUnpacker.ReadValueBitPacked(reader, out RemoteClientVersion); + ByteUnpacker.ReadValueBitPacked(reader, out RemoteClientSessionVersion); ByteUnpacker.ReadValueBitPacked(reader, out TickRate); reader.ReadValueSafe(out EnableSceneManagement); } @@ -41,6 +37,8 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade internal struct ConnectionRequestMessage : INetworkMessage { + internal const string InvalidSessionVersionMessage = "The client version is not compatible with the session version."; + // This version update is unidirectional (client to service) and version // handling occurs on the service side. This serialized data is never sent // to a host or server. @@ -48,7 +46,7 @@ internal struct ConnectionRequestMessage : INetworkMessage public int Version => k_SendClientConfigToService; public ulong ConfigHash; - public bool CMBServiceConnection; + public bool DistributedAuthority; public ClientConfig ClientConfig; public byte[] ConnectionData; @@ -73,7 +71,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // END FORBIDDEN SEGMENT // ============================================================ - if (CMBServiceConnection) + if (DistributedAuthority) { writer.WriteNetworkSerializable(ClientConfig); } @@ -121,6 +119,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int // END FORBIDDEN SEGMENT // ============================================================ + if (networkManager.DAHost) + { + reader.ReadNetworkSerializable(out ClientConfig); + } + if (networkManager.NetworkConfig.ConnectionApproval) { if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize())) @@ -183,6 +186,17 @@ public void Handle(ref NetworkContext context) var networkManager = (NetworkManager)context.SystemOwner; var senderId = context.SenderId; + // DAHost mocking the service logic to disconnect clients trying to connect with a lower session version + if (networkManager.DAHost) + { + if (ClientConfig.RemoteClientSessionVersion < networkManager.SessionConfig.SessionVersion) + { + //Disconnect with reason + networkManager.ConnectionManager.DisconnectClient(senderId, InvalidSessionVersionMessage); + return; + } + } + if (networkManager.ConnectionManager.PendingClients.TryGetValue(senderId, out PendingClient client)) { // Set to pending approval to prevent future connection requests from being approved diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 571fe4736e..2df9f1ffb6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1765,113 +1765,120 @@ internal void GetObjectDistribution(ref Dictionary>>(); - - // DA-NGO CMB SERVICE NOTES: - // This is optional, but I found it easier to get the total count of spawned objects for each prefab - // type contained in the previous table in order to be able to calculate the targeted object distribution - // count of that type per client. - var objectTypeCount = new Dictionary(); - - // Get all spawned objects by type and then by client owner that are spawned and can be distributed - GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); + if (!NetworkManager.DistributedAuthorityMode) + { + return; + } - var clientCount = NetworkManager.ConnectedClientsIds.Count; + if (NetworkManager.SessionConfig.ServiceSideDistribution) + { + return; + } - // Cycle through each prefab type - foreach (var objectTypeEntry in distributedNetworkObjects) + + // DA-NGO CMB SERVICE NOTES: + // The most basic object distribution should be broken up into a table of spawned object types + // where each type contains a list of each client's owned objects of that type that can be + // distributed. + // The table format: + // [GlobalObjectIdHashValue][ClientId][List of Owned Objects] + var distributedNetworkObjects = new Dictionary>>(); + + // DA-NGO CMB SERVICE NOTES: + // This is optional, but I found it easier to get the total count of spawned objects for each prefab + // type contained in the previous table in order to be able to calculate the targeted object distribution + // count of that type per client. + var objectTypeCount = new Dictionary(); + + // Get all spawned objects by type and then by client owner that are spawned and can be distributed + GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); + + var clientCount = NetworkManager.ConnectedClientsIds.Count; + + // Cycle through each prefab type + foreach (var objectTypeEntry in distributedNetworkObjects) + { + // Calculate the number of objects that should be distributed amongst the clients + var totalObjectsToDistribute = objectTypeCount[objectTypeEntry.Key]; + var objPerClientF = totalObjectsToDistribute * (1.0f / clientCount); + var floorValue = (int)Math.Floor(objPerClientF); + var fractional = objPerClientF - floorValue; + var objPerClient = 0; + if (fractional >= 0.556f) { - // Calculate the number of objects that should be distributed amongst the clients - var totalObjectsToDistribute = objectTypeCount[objectTypeEntry.Key]; - var objPerClientF = totalObjectsToDistribute * (1.0f / clientCount); - var floorValue = (int)Math.Floor(objPerClientF); - var fractional = objPerClientF - floorValue; - var objPerClient = 0; - if (fractional >= 0.556f) - { - objPerClient = (int)Math.Round(totalObjectsToDistribute * (1.0f / clientCount)); - } - else - { - objPerClient = floorValue; - } + objPerClient = (int)Math.Round(totalObjectsToDistribute * (1.0f / clientCount)); + } + else + { + objPerClient = floorValue; + } + + // If the object per client count is zero, then move to the next type. + if (objPerClient <= 0) + { + continue; + } - // If the object per client count is zero, then move to the next type. - if (objPerClient <= 0) + // Evenly distribute this object type amongst the clients + foreach (var ownerList in objectTypeEntry.Value) + { + if (ownerList.Value.Count <= 1) { continue; } - // Evenly distribute this object type amongst the clients - foreach (var ownerList in objectTypeEntry.Value) - { - if (ownerList.Value.Count <= 1) - { - continue; - } - - var maxDistributeCount = Mathf.Max(ownerList.Value.Count - objPerClient, 1); - var distributed = 0; + var maxDistributeCount = Mathf.Max(ownerList.Value.Count - objPerClient, 1); + var distributed = 0; - // For now when we have more players then distributed NetworkObjects that - // a specific client owns, just assign half of the NetworkObjects to the new client - var offsetCount = Mathf.Max((int)Math.Round((float)(ownerList.Value.Count / objPerClient)), 1); - if (EnableDistributeLogging) - { - Debug.Log($"[{objPerClient} of {totalObjectsToDistribute}][Client-{ownerList.Key}] Count: {ownerList.Value.Count} | ObjPerClient: {objPerClient} | maxD: {maxDistributeCount} | Offset: {offsetCount}"); - } + // For now when we have more players then distributed NetworkObjects that + // a specific client owns, just assign half of the NetworkObjects to the new client + var offsetCount = Mathf.Max((int)Math.Round((float)(ownerList.Value.Count / objPerClient)), 1); + if (EnableDistributeLogging) + { + Debug.Log($"[{objPerClient} of {totalObjectsToDistribute}][Client-{ownerList.Key}] Count: {ownerList.Value.Count} | ObjPerClient: {objPerClient} | maxD: {maxDistributeCount} | Offset: {offsetCount}"); + } - for (int i = 0; i < ownerList.Value.Count; i++) + for (int i = 0; i < ownerList.Value.Count; i++) + { + if ((i % offsetCount) == 0) { - if ((i % offsetCount) == 0) + ChangeOwnership(ownerList.Value[i], clientId, true); + if (EnableDistributeLogging) { - ChangeOwnership(ownerList.Value[i], clientId, true); - if (EnableDistributeLogging) - { - Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}"); - } - distributed++; - } - if (distributed == maxDistributeCount) - { - break; + Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}"); } + distributed++; + } + if (distributed == maxDistributeCount) + { + break; } } } + } - // If EnableDistributeLogging is enabled, log the object type distribution counts per client - if (EnableDistributeLogging) + // If EnableDistributeLogging is enabled, log the object type distribution counts per client + if (EnableDistributeLogging) + { + var builder = new StringBuilder(); + distributedNetworkObjects.Clear(); + objectTypeCount.Clear(); + GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); + builder.AppendLine($"Client Relative Distributed Object Count: (distribution follows)"); + // Cycle through each prefab type + foreach (var objectTypeEntry in distributedNetworkObjects) { - var builder = new StringBuilder(); - distributedNetworkObjects.Clear(); - objectTypeCount.Clear(); - GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); - builder.AppendLine($"Client Relative Distributed Object Count: (distribution follows)"); - // Cycle through each prefab type - foreach (var objectTypeEntry in distributedNetworkObjects) + builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}][Total Count: {objectTypeCount[objectTypeEntry.Key]}]"); + builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}] Distribution:"); + // Evenly distribute this type amongst clients + foreach (var ownerList in objectTypeEntry.Value) { - builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}][Total Count: {objectTypeCount[objectTypeEntry.Key]}]"); - builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}] Distribution:"); - // Evenly distribute this type amongst clients - foreach (var ownerList in objectTypeEntry.Value) - { - builder.AppendLine($"[Client-{ownerList.Key}] Count: {ownerList.Value.Count}"); - } + builder.AppendLine($"[Client-{ownerList.Key}] Count: {ownerList.Value.Count}"); } - Debug.Log(builder.ToString()); } + Debug.Log(builder.ToString()); } + } internal struct DeferredDespawnObject diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs new file mode 100644 index 0000000000..cb37362f44 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs @@ -0,0 +1,106 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + internal class SessionVersionConnectionRequest : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + public SessionVersionConnectionRequest() : base(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost) { } + + private bool m_UseValidSessionVersion; + private bool m_ClientWasDisconnected; + private NetworkManager m_ClientNetworkManager; + + /// + /// Callback used to mock the scenario where a client has an invalid session version + /// + /// + private SessionConfig GetInavlidSessionConfig() + { + return new SessionConfig(m_ServerNetworkManager.SessionConfig.SessionVersion - 1); + } + + /// + /// Overriding this method allows us to configure the newly instantiated client's + /// NetworkManager prior to it being started. + /// + /// the newly instantiated NetworkManager + protected override void OnNewClientCreated(NetworkManager networkManager) + { + m_ClientWasDisconnected = false; + m_ClientNetworkManager = networkManager; + m_ClientNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; + if (!m_UseValidSessionVersion) + { + networkManager.OnGetSessionConfig = GetInavlidSessionConfig; + } + base.OnNewClientCreated(networkManager); + } + + /// + /// Tracks if the client was disconnected or not + /// + private void OnClientDisconnectCallback(ulong clientId) + { + m_ClientWasDisconnected = true; + m_ClientNetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; + } + + /// + /// This handles disabling the internal integration test logic that waits for + /// clients to be connected. When we know the client will be disconnected, + /// we want to have the NetcodeIntegrationTest not wait for the client to + /// connect (otherwise it will timeout there and fail the test). + /// + /// + /// true = wait | false = don't wait + protected override bool ShouldWaitForNewClientToConnect(NetworkManager networkManager) + { + return m_UseValidSessionVersion; + } + + /// + /// Validates that when the client's session config version is valid a client will be + /// allowed to connect and when it is not valid the client will be disconnected. + /// + /// + /// This is just a mock of the service logic to validate everything on the NGO side is + /// working correctly. + /// + /// true = use valid session version | false = use invalid session version + [UnityTest] + public IEnumerator ValidateSessionVersion([Values] bool useValidSessionVersion) + { + // Test client being disconnected due to invalid session version + m_UseValidSessionVersion = useValidSessionVersion; + yield return CreateAndStartNewClient(); + yield return s_DefaultWaitForTick; + if (!m_UseValidSessionVersion) + { + yield return WaitForConditionOrTimeOut(() => m_ClientWasDisconnected); + AssertOnTimeout("Client was not disconnected when it should have been!"); + Assert.True(m_ClientNetworkManager.DisconnectReason == ConnectionRequestMessage.InvalidSessionVersionMessage, "Client did not receive the correct invalid session version message!"); + } + else + { + Assert.False(m_ClientWasDisconnected, "Client was disconnected when it was expected to connect!"); + Assert.True(m_ClientNetworkManager.IsConnectedClient, "Client did not connect properly using the correct session version!"); + } + } + + /// + /// Invoked at the end of each integration test pass. + /// Primarily used to clean up for the next pass. + /// + protected override IEnumerator OnTearDown() + { + m_ClientNetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; + m_ClientNetworkManager = null; + yield return base.OnTearDown(); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs.meta new file mode 100644 index 0000000000..207359dcc0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SessionVersionConnectionRequest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 62913c78d28137141acc47ea8f63e657 \ No newline at end of file From d7df73b0ea3e249f9827a35a6300c67280ec252f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 2 Dec 2024 18:51:16 -0600 Subject: [PATCH 135/236] fix: networklist editor memory leak (#3147) * fix This fixes the issue with NetworkLists on in-scene placed NetworkObjects causing small memory leaks when entering and exiting playmode. * update adding change log entry * style removing white spaces * style removing added CR/LF entries. * update adding PR number to log entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../NetworkVariable/Collections/NetworkList.cs | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9f2f95e72e..56e82e65e1 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `NetworkList` properties on in-scene placed `NetworkObject`s could cause small memory leaks when entering playmode. (#3147) - Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133) - Fixed issue where a `NetworkManager` started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133) - Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 203632c184..14c6646961 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -50,6 +50,11 @@ public NetworkList(IEnumerable values = default, } } + ~NetworkList() + { + Dispose(); + } + /// public override void ResetDirty() { @@ -624,8 +629,16 @@ public int LastModifiedTick ///
public override void Dispose() { - m_List.Dispose(); - m_DirtyEvents.Dispose(); + if (m_List.IsCreated) + { + m_List.Dispose(); + } + + if (m_DirtyEvents.IsCreated) + { + m_DirtyEvents.Dispose(); + } + base.Dispose(); } } From 218a647f74b22b97f8c5dc8856987f5ecb34c9f1 Mon Sep 17 00:00:00 2001 From: Dominick Date: Tue, 3 Dec 2024 10:01:58 -0700 Subject: [PATCH 136/236] fix: make ClientConfig version signed (#3150) --- .../Runtime/Messaging/Messages/ConnectionRequestMessage.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index 4674b32961..11aa4f35bc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -8,12 +8,12 @@ namespace Unity.Netcode internal struct ClientConfig : INetworkSerializable { public SessionConfig SessionConfig; - public uint SessionVersion => SessionConfig.SessionVersion; + public int SessionVersion => (int)SessionConfig.SessionVersion; public uint TickRate; public bool EnableSceneManagement; // Only gets deserialized but should never be used unless testing - public uint RemoteClientSessionVersion; + public int RemoteClientSessionVersion; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { From 3c4f1ccee720657365d6fd8a33c3bf7612b0fa89 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 6 Dec 2024 16:46:42 -0500 Subject: [PATCH 137/236] feat: Remove DA NetworkVariable modifications (#3155) --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Core/NetworkBehaviour.cs | 42 +-- .../Messaging/NetworkMessageManager.cs | 22 -- .../Collections/NetworkList.cs | 24 -- .../NetworkVariable/NetworkVariable.cs | 6 +- .../NetworkVariable/NetworkVariableBase.cs | 4 +- .../NetworkVariable/NetworkVariableTypes.cs | 40 --- .../NetworkVariableTypes.cs.meta | 3 - .../Serialization/FallbackSerializer.cs | 8 - .../INetworkVariableSerializer.cs | 22 +- .../NetworkVariableSerialization.cs | 50 +-- .../TypedSerializerImplementations.cs | 303 ------------------ .../VariableSerializationTools.cs | 54 ---- .../VariableSerializationTools.cs.meta | 3 - .../NetworkVariable/NetworkVariableTests.cs | 7 +- 15 files changed, 17 insertions(+), 573 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs delete mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta delete mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs delete mode 100644 com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 56e82e65e1..49ad3bba83 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118) - Fixed issue where `NetworkList` properties on in-scene placed `NetworkObject`s could cause small memory leaks when entering playmode. (#3147) - Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133) - Fixed issue where a `NetworkManager` started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133) @@ -26,6 +27,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Optimised `NetworkVariable` and `NetworkTransform` related packets when in Distributed Authority mode. - The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121) ## [2.1.1] - 2024-10-18 diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index bcdd25fd95..adc7ec0fa4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -826,7 +826,7 @@ public virtual void OnGainedOwnership() { } internal void InternalOnGainedOwnership() { UpdateNetworkProperties(); - // New owners need to assure any NetworkVariables they have write permissions + // New owners need to assure any NetworkVariables they have write permissions // to are updated so the previous and original values are aligned with the // current value (primarily for collections). if (OwnerClientId == NetworkManager.LocalClientId) @@ -1181,14 +1181,8 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie { // Create any values that require accessing the NetworkManager locally (it is expensive to access it in NetworkBehaviour) var networkManager = NetworkManager; - var distributedAuthority = networkManager.DistributedAuthorityMode; var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; - // Always write the NetworkVariable count even if zero for distributed authority (used by comb server) - if (distributedAuthority) - { - writer.WriteValueSafe((ushort)NetworkVariableFields.Count); - } // Exit early if there are no NetworkVariables if (NetworkVariableFields.Count == 0) @@ -1203,14 +1197,8 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie if (NetworkVariableFields[j].CanClientRead(targetClientId)) { // Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode - if (ensureLengthSafety || distributedAuthority) + if (ensureLengthSafety) { - // Write the type being serialized for distributed authority (only for comb-server) - if (distributedAuthority) - { - writer.WriteValueSafe(NetworkVariableFields[j].Type); - } - var writePos = writer.Position; // Note: This value can't be packed because we don't know how large it will be in advance // we reserve space for it, then write the data, then come back and fill in the space @@ -1261,20 +1249,8 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { // Stack cache any values that requires accessing the NetworkManager (it is expensive to access it in NetworkBehaviour) var networkManager = NetworkManager; - var distributedAuthority = networkManager.DistributedAuthorityMode; var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; - // Always read the NetworkVariable count when in distributed authority (sanity check if comb-server matches what client has locally) - if (distributedAuthority) - { - reader.ReadValueSafe(out ushort variableCount); - if (variableCount != NetworkVariableFields.Count) - { - Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}] NetworkVariable count mismatch! (Read: {variableCount} vs. Expected: {NetworkVariableFields.Count})"); - return; - } - } - // Exit early if nothing else to read if (NetworkVariableFields.Count == 0) { @@ -1289,14 +1265,8 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) // Distributed Authority: All clients have read permissions, always try to read the value if (NetworkVariableFields[j].CanClientRead(clientId)) { - if (ensureLengthSafety || distributedAuthority) + if (ensureLengthSafety) { - // Read the type being serialized and discard it (for now) when in a distributed authority network topology (only used by comb-server) - if (distributedAuthority) - { - reader.ReadValueSafe(out NetworkVariableType _); - } - reader.ReadValueSafe(out varSize); if (varSize == 0) { @@ -1320,11 +1290,11 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) continue; } - // Read the NetworkVarible value + // Read the NetworkVariable value NetworkVariableFields[j].ReadField(reader); - // When EnsureNetworkVariableLengthSafety or DistributedAuthorityMode always do a bounds check - if (ensureLengthSafety || distributedAuthority) + // When EnsureNetworkVariableLengthSafety always do a bounds check + if (ensureLengthSafety) { if (reader.Position > (readStartPos + varSize)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 1850725599..661fbd74f5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -141,28 +141,6 @@ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetwor { RegisterMessageType(type); } - -#if UNITY_EDITOR - if (EnableMessageOrderConsoleLog) - { - // DANGO-TODO: Remove this when we have some form of message type indices stability in place - // For now, just log the messages and their assigned types for reference purposes. - var networkManager = m_Owner as NetworkManager; - if (networkManager != null) - { - if (networkManager.DistributedAuthorityMode) - { - var messageListing = new StringBuilder(); - messageListing.AppendLine("NGO Message Index to Type Listing:"); - foreach (var message in m_MessageTypes) - { - messageListing.AppendLine($"[{message.Value}][{message.Key.Name}]"); - } - Debug.Log(messageListing); - } - } - } -#endif } catch (Exception) { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 14c6646961..4a3499730f 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -24,7 +24,6 @@ public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatabl /// The callback to be invoked when the list gets changed ///
public event OnListChangedDelegate OnListChanged; - internal override NetworkVariableType Type => NetworkVariableType.NetworkList; /// /// Constructor method for @@ -136,20 +135,6 @@ public override void WriteDelta(FastBufferWriter writer) /// public override void WriteField(FastBufferWriter writer) { - if (m_NetworkManager.DistributedAuthorityMode) - { - writer.WriteValueSafe(NetworkVariableSerialization.Serializer.Type); - if (NetworkVariableSerialization.Serializer.Type == NetworkVariableType.Unmanaged) - { - // Write the size of the unmanaged serialized type as it has a fixed size. This allows the CMB runtime to correctly read the unmanged type. - var placeholder = new T(); - var startPos = writer.Position; - NetworkVariableSerialization.Serializer.Write(writer, ref placeholder); - var size = writer.Position - startPos; - writer.Seek(startPos); - BytePacker.WriteValueBitPacked(writer, size); - } - } writer.WriteValueSafe((ushort)m_List.Length); for (int i = 0; i < m_List.Length; i++) { @@ -161,15 +146,6 @@ public override void WriteField(FastBufferWriter writer) public override void ReadField(FastBufferReader reader) { m_List.Clear(); - if (m_NetworkManager.DistributedAuthorityMode) - { - SerializationTools.ReadType(reader, NetworkVariableSerialization.Serializer); - // Collection item type is used by the DA server, drop value here. - if (NetworkVariableSerialization.Serializer.Type == NetworkVariableType.Unmanaged) - { - ByteUnpacker.ReadValueBitPacked(reader, out int _); - } - } reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index a030884a09..c712a90223 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -45,8 +45,6 @@ public override void OnInitialize() NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); } - internal override NetworkVariableType Type => NetworkVariableType.Value; - /// /// Constructor for /// @@ -92,7 +90,7 @@ public void Reset(T value = default) // The introduction of standard .NET collections caused an issue with permissions since there is no way to detect changes in the // collection without doing a full comparison. While this approach does consume more memory per collection instance, it is the // lowest risk approach to resolving the issue where a client with no write permissions could make changes to a collection locally - // which can cause a myriad of issues. + // which can cause a myriad of issues. private protected T m_InternalOriginalValue; private protected T m_PreviousValue; @@ -305,7 +303,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) /// This should be always invoked (client & server) to assure the previous values are set /// !! IMPORTANT !! /// When a server forwards delta updates to connected clients, it needs to preserve the previous dirty value(s) - /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked + /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked /// after it is done forwarding the deltas at the end of the method. /// internal override void PostDeltaRead() diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 88023978ac..537d3eba38 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -35,8 +35,6 @@ public abstract class NetworkVariableBase : IDisposable private NetworkManager m_InternalNetworkManager; - internal virtual NetworkVariableType Type => NetworkVariableType.Unknown; - internal string GetWritePermissionError() { return $"|Client-{m_NetworkManager.LocalClientId}|{m_NetworkBehaviour.name}|{Name}| Write permissions ({WritePerm}) for this client instance is not allowed!"; @@ -351,7 +349,7 @@ internal ulong OwnerClientId() /// This should be always invoked (client & server) to assure the previous values are set /// !! IMPORTANT !! /// When a server forwards delta updates to connected clients, it needs to preserve the previous dirty value(s) - /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked + /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked /// after it is done forwarding the deltas at the end of the method. ///
internal virtual void PostDeltaRead() diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs deleted file mode 100644 index d8b6912ba3..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs +++ /dev/null @@ -1,40 +0,0 @@ -#if UNITY_EDITOR -#endif - -namespace Unity.Netcode -{ - /// - /// Enum representing the different types of Network Variables that can be sent over the network. - /// The values cannot be changed, as they are used to serialize and deserialize variables on the DA server. - /// Adding new variables should be done by adding new values to the end of the enum - /// using the next free value. - /// - /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - /// Add any new Variable types to this table at the END with incremented index value - /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - internal enum NetworkVariableType : byte - { - /// - /// Value - /// Used for all of the basic NetworkVariables that contain a single value - /// - Value = 0, - /// - /// For any type that is not known at runtime - /// - Unknown = 1, - /// - /// NetworkList - /// - NetworkList = 2, - - // The following types are valid types inside of NetworkVariable collections - Short = 11, - UShort = 12, - Int = 13, - UInt = 14, - Long = 15, - ULong = 16, - Unmanaged = 17, - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta deleted file mode 100644 index 4206dbc962..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableTypes.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: df4a4005f1c842669f94a404019400ed -timeCreated: 1718292058 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs index 0b1b2c10a3..5e855745c0 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/FallbackSerializer.cs @@ -14,9 +14,6 @@ namespace Unity.Netcode /// internal class FallbackSerializer : INetworkVariableSerializer { - public NetworkVariableType Type => NetworkVariableType.Unknown; - public bool IsDistributedAuthorityOptimized => true; - private void ThrowArgumentError() { throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); @@ -82,11 +79,6 @@ public void Duplicate(in T value, ref T duplicatedValue) } UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); } - - public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) => ThrowArgumentError(); - public void ReadDistributedAuthority(FastBufferReader reader, ref T value) => ThrowArgumentError(); - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => ThrowArgumentError(); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => ThrowArgumentError(); } // RuntimeAccessModifiersILPP will make this `public` diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs index 571d1dc5f0..d515c0c309 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/INetworkVariableSerializer.cs @@ -2,28 +2,8 @@ namespace Unity.Netcode { - /// - /// Interface used by NetworkVariables to serialize them with additional information for the DA runtime - /// - /// /// - internal interface IDistributedAuthoritySerializer - { - /// - /// The Type tells the DA server how to parse this type. - /// The user should never be able to override this value, as it is meaningful for the DA server - /// - public NetworkVariableType Type { get; } - public bool IsDistributedAuthorityOptimized { get; } - public void WriteDistributedAuthority(FastBufferWriter writer, ref T value); - public void ReadDistributedAuthority(FastBufferReader reader, ref T value); - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value); - } - - - /// - internal interface INetworkVariableSerializer : IDistributedAuthoritySerializer + internal interface INetworkVariableSerializer { // Write has to be taken by ref here because of INetworkSerializable // Open Instance Delegates (pointers to methods without an instance attached to them) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs index bc0f79fa24..8401df81af 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -14,8 +14,6 @@ public static class NetworkVariableSerialization { internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); - internal static bool IsDistributedAuthority => NetworkManager.IsDistributedAuthority; - /// /// A callback to check if two values are equal. /// @@ -53,20 +51,7 @@ public static class NetworkVariableSerialization /// public static void Write(FastBufferWriter writer, ref T value) { - if (IsDistributedAuthority) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (!NetworkManager.DisableNotOptimizedSerializedType && !Serializer.IsDistributedAuthorityOptimized) - { - NetworkManager.LogSerializedTypeNotOptimized(); - } -#endif - Serializer.WriteDistributedAuthority(writer, ref value); - } - else - { - Serializer.Write(writer, ref value); - } + Serializer.Write(writer, ref value); } /// @@ -91,14 +76,7 @@ public static void Write(FastBufferWriter writer, ref T value) /// public static void Read(FastBufferReader reader, ref T value) { - if (IsDistributedAuthority) - { - Serializer.ReadDistributedAuthority(reader, ref value); - } - else - { - Serializer.Read(reader, ref value); - } + Serializer.Read(reader, ref value); } /// @@ -120,20 +98,7 @@ public static void Read(FastBufferReader reader, ref T value) /// public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) { - if (IsDistributedAuthority) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (!NetworkManager.DisableNotOptimizedSerializedType && !Serializer.IsDistributedAuthorityOptimized) - { - NetworkManager.LogSerializedTypeNotOptimized(); - } -#endif - Serializer.WriteDeltaDistributedAuthority(writer, ref value, ref previousValue); - } - else - { - Serializer.WriteDelta(writer, ref value, ref previousValue); - } + Serializer.WriteDelta(writer, ref value, ref previousValue); } /// @@ -158,14 +123,7 @@ public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previo /// public static void ReadDelta(FastBufferReader reader, ref T value) { - if (IsDistributedAuthority) - { - Serializer.ReadDeltaDistributedAuthority(reader, ref value); - } - else - { - Serializer.ReadDelta(reader, ref value); - } + Serializer.ReadDelta(reader, ref value); } /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs index 9f5a1042e0..49e7e5e7cf 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -10,21 +10,6 @@ namespace Unity.Netcode /// internal class ShortSerializer : INetworkVariableSerializer { - public NetworkVariableType Type => NetworkVariableType.Short; - public bool IsDistributedAuthorityOptimized => true; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref short value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref short value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref short value, ref short previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref short value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref short value) { BytePacker.WriteValueBitPacked(writer, value); @@ -61,20 +46,6 @@ public void Duplicate(in short value, ref short duplicatedValue) /// internal class UshortSerializer : INetworkVariableSerializer { - public NetworkVariableType Type => NetworkVariableType.UShort; - public bool IsDistributedAuthorityOptimized => true; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref ushort value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref ushort value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ushort value, ref ushort previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref ushort value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref ushort value) { BytePacker.WriteValueBitPacked(writer, value); @@ -111,20 +82,6 @@ public void Duplicate(in ushort value, ref ushort duplicatedValue) /// internal class IntSerializer : INetworkVariableSerializer { - public NetworkVariableType Type => NetworkVariableType.Int; - public bool IsDistributedAuthorityOptimized => true; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref int value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref int value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref int value, ref int previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref int value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref int value) { BytePacker.WriteValueBitPacked(writer, value); @@ -161,20 +118,6 @@ public void Duplicate(in int value, ref int duplicatedValue) /// internal class UintSerializer : INetworkVariableSerializer { - public NetworkVariableType Type => NetworkVariableType.UInt; - public bool IsDistributedAuthorityOptimized => true; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref uint value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref uint value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref uint value, ref uint previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref uint value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref uint value) { BytePacker.WriteValueBitPacked(writer, value); @@ -211,20 +154,6 @@ public void Duplicate(in uint value, ref uint duplicatedValue) ///
internal class LongSerializer : INetworkVariableSerializer { - public NetworkVariableType Type => NetworkVariableType.Long; - public bool IsDistributedAuthorityOptimized => true; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref long value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref long value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref long value, ref long previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref long value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref long value) { BytePacker.WriteValueBitPacked(writer, value); @@ -261,21 +190,6 @@ public void Duplicate(in long value, ref long duplicatedValue) ///
internal class UlongSerializer : INetworkVariableSerializer { - public NetworkVariableType Type => NetworkVariableType.ULong; - public bool IsDistributedAuthorityOptimized => true; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref ulong value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref ulong value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ulong value, ref ulong previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref ulong value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref ulong value) { BytePacker.WriteValueBitPacked(writer, value); @@ -317,21 +231,6 @@ public void Duplicate(in ulong value, ref ulong duplicatedValue) /// internal class UnmanagedTypeSerializer : INetworkVariableSerializer where T : unmanaged { - public NetworkVariableType Type => NetworkVariableType.Unmanaged; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref T value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref T value) { writer.WriteUnmanagedSafe(value); @@ -365,20 +264,6 @@ public void Duplicate(in T value, ref T duplicatedValue) internal class ListSerializer : INetworkVariableSerializer> { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref List value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref List value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref List value, ref List previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref List value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref List value) { var isNull = value == null; @@ -468,20 +353,6 @@ public void Duplicate(in List value, ref List duplicatedValue) internal class HashSetSerializer : INetworkVariableSerializer> where T : IEquatable { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref HashSet value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref HashSet value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref HashSet value, ref HashSet previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref HashSet value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref HashSet value) { var isNull = value == null; @@ -563,20 +434,6 @@ public void Duplicate(in HashSet value, ref HashSet duplicatedValue) internal class DictionarySerializer : INetworkVariableSerializer> where TKey : IEquatable { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref Dictionary value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref Dictionary value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref Dictionary value, ref Dictionary previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref Dictionary value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref Dictionary value) { var isNull = value == null; @@ -659,20 +516,6 @@ public void Duplicate(in Dictionary value, ref Dictionary : INetworkVariableSerializer> where T : unmanaged { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteUnmanagedSafe(value); @@ -718,20 +561,6 @@ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValu #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT internal class UnmanagedListSerializer : INetworkVariableSerializer> where T : unmanaged { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteUnmanagedSafe(value); @@ -775,21 +604,6 @@ public void Duplicate(in NativeList value, ref NativeList duplicatedValue) internal class NativeHashSetSerializer : INetworkVariableSerializer> where T : unmanaged, IEquatable { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeHashSet value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashSet value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashSet value, ref NativeHashSet previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeHashSet value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref NativeHashSet value) { writer.WriteValueSafe(value); @@ -835,21 +649,6 @@ internal class NativeHashMapSerializer : INetworkVariableSerializer< where TKey : unmanaged, IEquatable where TVal : unmanaged { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeHashMap value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashMap value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashMap value, ref NativeHashMap previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeHashMap value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref NativeHashMap value) { writer.WriteValueSafe(value); @@ -897,21 +696,6 @@ public void Duplicate(in NativeHashMap value, ref NativeHashMap internal class FixedStringSerializer : INetworkVariableSerializer where T : unmanaged, INativeList, IUTF8Bytes { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref T value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref T value) { writer.WriteValueSafe(value); @@ -1019,20 +803,6 @@ public void Duplicate(in T value, ref T duplicatedValue) /// internal class FixedStringArraySerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteValueSafe(value); @@ -1083,21 +853,6 @@ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValu /// internal class FixedStringListSerializer : INetworkVariableSerializer> where T : unmanaged, INativeList, IUTF8Bytes { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteValueSafe(value); @@ -1145,21 +900,6 @@ public void Duplicate(in NativeList value, ref NativeList duplicatedValue) /// internal class UnmanagedNetworkSerializableSerializer : INetworkVariableSerializer where T : unmanaged, INetworkSerializable { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref T value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); @@ -1212,20 +952,6 @@ public void Duplicate(in T value, ref T duplicatedValue) /// internal class UnmanagedNetworkSerializableArraySerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref NativeArray value) { writer.WriteNetworkSerializable(value); @@ -1276,21 +1002,6 @@ public void Duplicate(in NativeArray value, ref NativeArray duplicatedValu /// internal class UnmanagedNetworkSerializableListSerializer : INetworkVariableSerializer> where T : unmanaged, INetworkSerializable { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList value, ref NativeList previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList value) => Read(reader, ref value); - public void Write(FastBufferWriter writer, ref NativeList value) { writer.WriteNetworkSerializable(value); @@ -1338,20 +1049,6 @@ public void Duplicate(in NativeList value, ref NativeList duplicatedValue) /// internal class ManagedNetworkSerializableSerializer : INetworkVariableSerializer where T : class, INetworkSerializable, new() { - public NetworkVariableType Type => NetworkVariableType.Value; - public bool IsDistributedAuthorityOptimized => false; - - public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) - { - Write(writer, ref value); - } - - public void ReadDistributedAuthority(FastBufferReader reader, ref T value) - { - Read(reader, ref value); - } - public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value); - public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value); public void Write(FastBufferWriter writer, ref T value) { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs deleted file mode 100644 index 50ea37d1b0..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; - -namespace Unity.Netcode -{ - - internal static class SerializationTools - { - public delegate void WriteDelegate(FastBufferWriter writer, ref T value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteWithSize(WriteDelegate writeMethod, FastBufferWriter writer, ref T value) - { - var writePos = writer.Position; - // Note: This value can't be packed because we don't know how large it will be in advance - // we reserve space for it, then write the data, then come back and fill in the space - // to pack here, we'd have to write data to a temporary buffer and copy it in - which - // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... - // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. - writer.WriteValueSafe((ushort)0); - var startPos = writer.Position; - writeMethod(writer, ref value); - var size = writer.Position - startPos; - writer.Seek(writePos); - writer.WriteValueSafe((ushort)size); - writer.Seek(startPos + size); - } - - public delegate void ReadDelegate(FastBufferReader writer, ref T value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadWithSize(ReadDelegate readMethod, FastBufferReader reader, ref T value) - { - reader.ReadValueSafe(out ushort _); - readMethod(reader, ref value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteType(FastBufferWriter writer, NetworkVariableType type) => writer.WriteValueSafe(type); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReadType(FastBufferReader reader, INetworkVariableSerializer serializer) - { - reader.ReadValueSafe(out NetworkVariableType type); - if (type != serializer.Type) - { - throw new SerializationException(); - } - } - - - } - -} diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta deleted file mode 100644 index 006046fa77..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/VariableSerializationTools.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 52a4ce368df54b0a8887c08f3402bcd3 -timeCreated: 1718300602 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index d67d20797d..a67be4de26 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -1012,8 +1012,7 @@ public void TestUnsupportedManagedTypesThrowExceptions() } [Test] - // Exceptions should be thrown in DA mode with UserNetworkVariableSerialization - public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptionsInClientServerMode() + public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions() { var variable = new NetworkVariable(); UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out string value) => @@ -1039,10 +1038,6 @@ public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions variable.ReadField(reader); Assert.AreEqual("012345", variable.Value); } - catch (Exception) - { - Assert.True(NetworkVariableSerialization>.IsDistributedAuthority); - } finally { UserNetworkVariableSerialization.ReadValue = null; From f170341efa175795c92ae0383805214498adab35 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 6 Dec 2024 19:28:41 -0600 Subject: [PATCH 138/236] chore: api documentation phase 1 (#3156) * update Adding and updating xml API documentation for ConnectionEvent and ConnectionEventData * update Adding XML API documentation for RpcTargetUse. * Update RpcTarget.cs Apply adjustments from backport to this. --- .../Connection/NetworkConnectionManager.cs | 41 ++++++++++++++++++- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 19 +++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 4296a6835a..09119e8076 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -10,15 +10,54 @@ namespace Unity.Netcode { - + /// + /// The connection event type set within to signify the type of connection event notification received. + /// + /// + /// is returned as a parameter of the event notification. + /// and event types occur on the client-side of the newly connected client and on the server-side.
+ /// and event types occur on connected clients to notify that a new client (peer) has joined/connected. + ///
public enum ConnectionEvent { + /// + /// This event is set on the client-side of the newly connected client and on the server-side.
+ ///
+ /// + /// On the newly connected client side, the will be the .
+ /// On the server side, the will be the ID of the client that just connected. + ///
ClientConnected, + /// + /// This event is set on clients that are already connected to the session. + /// + /// + /// The will be the ID of the client that just connected. + /// PeerConnected, + /// + /// This event is set on the client-side of the client that disconnected client and on the server-side. + /// + /// + /// On the disconnected client side, the will be the .
+ /// On the server side, this will be the ID of the client that disconnected. + ///
ClientDisconnected, + /// + /// This event is set on clients that are already connected to the session. + /// + /// + /// The will be the ID of the client that just disconnected. + /// PeerDisconnected } + /// + /// Returned as a parameter of the event notification. + /// + /// + /// See for more details on the types of connection events received. + /// public struct ConnectionEventData { public ConnectionEvent EventType; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index c5706a8b31..2864205769 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -79,9 +79,28 @@ public enum SendTo SpecifiedInParams } + /// + /// This parameter configures a performance optimization. This optimization is not valid in all situations.
+ /// Because BaseRpcTarget is a managed type, allocating a new one is expensive, as it puts pressure on the garbage collector. + ///
+ /// + /// When using a allocation type for the RPC target(s):
+ /// You typically don't need to worry about persisting the generated. + /// When using a allocation type for the RPC target(s):
+ /// You will want to use , which returns , during initialization (i.e. ) and it to a property.
+ /// Then, When invoking the RPC, you would use your which is a persisted allocation of a given set of client identifiers. + /// !! Important !!
+ /// You will want to invoke of any persisted properties created via when despawning or destroying the associated component's . Not doing so will result in small memory leaks. + ///
public enum RpcTargetUse { + /// + /// Creates a temporary used for the frame an decorated method is invoked. + /// Temp, + /// + /// Creates a persisted that does not change and will persist until is called. + /// Persistent } From 88eaa08d380bdbaa7dca03062193158ddab325d3 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 9 Dec 2024 17:29:58 -0600 Subject: [PATCH 139/236] fix: distributed authority spawning prefab overrides (#3160) * fix All clients in distributed authority should invoke CheckForGlobalObjectIdHashOverride when spawning a NetworkPrefab since each client has the authority to spawn (i.e. each should be treated like a host/server in regards to spawning and prefab overrides). * update adding changelog entry. * update adding changelog PR number. * test updating existing test to run through with distributed authority and client-server network topologies. (still need one more integration test to validate the final spawned object on the non-authority sides) * style removing whitespace * fix Fixed issue with server only NetworkManager instance spawning the network prefab itself as opposed to creating an instance. Extracted the instantiation portion of a network prefab into an internal method that can be used for integration testing prefab instantiation and potentially other edge case scenarios. * test Migrated all runtime network prefab related tests into a Prefabs subfolder. Added new NetworkPrefabOverrideTests to validate the actual spawned instances on all clients using both client-server and distributed authority network topologies. Added helper method `NettcodeIntegrationTestHelpers.CreateNetworkObject`. * style correcting some typos in comments --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Core/NetworkObject.cs | 5 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 45 ++- .../Runtime/NetcodeIntegrationTestHelpers.cs | 31 +- .../Tests/Runtime/Prefabs.meta | 8 + .../{ => Prefabs}/AddNetworkPrefabTests.cs | 0 .../AddNetworkPrefabTests.cs.meta | 0 .../NetworkPrefabHandlerTests.cs | 9 +- .../NetworkPrefabHandlerTests.cs.meta | 0 .../Prefabs/NetworkPrefabOverrideTests.cs | 268 ++++++++++++++++++ .../NetworkPrefabOverrideTests.cs.meta | 2 + 11 files changed, 351 insertions(+), 19 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Prefabs.meta rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Prefabs}/AddNetworkPrefabTests.cs (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Prefabs}/AddNetworkPrefabTests.cs.meta (100%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Prefabs}/NetworkPrefabHandlerTests.cs (97%) rename com.unity.netcode.gameobjects/Tests/Runtime/{ => Prefabs}/NetworkPrefabHandlerTests.cs.meta (100%) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 49ad3bba83..228a4217ea 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where a sever only `NetworkManager` instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160) +- Fixed issue where only the session owner (as opposed to all clients) would handle spawning prefab overrides properly when using a distributed authority network topology. (#3160) - Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118) - Fixed issue where `NetworkList` properties on in-scene placed `NetworkObject`s could cause small memory leaks when entering playmode. (#3147) - Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 661c1ce41b..501cda138d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3246,14 +3246,15 @@ internal void UpdateForSceneChanges() } /// - /// Only applies to Hosts or session owners (for now) + /// Client-Server: Only applies to spawn authority (i.e. Server) + /// Distributed Authority: Applies to all clients since they all have spawn authority. /// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists. /// Server and Clients will always return the NetworkObject's GlobalObjectIdHash. /// /// appropriate hash value internal uint CheckForGlobalObjectIdHashOverride() { - if (NetworkManager.IsServer || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner)) + if (NetworkManager.IsServer || NetworkManager.DistributedAuthorityMode) { if (NetworkManager.PrefabHandler.ContainsHandler(this)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 2df9f1ffb6..9ce6595c5f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -736,12 +736,20 @@ public NetworkObject InstantiateAndSpawn(NetworkObject networkPrefab, ulong owne internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networkPrefab, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default) { var networkObject = networkPrefab; - // Host spawns the ovveride and server spawns the original prefab unless forceOverride is set to true where both server or host will spawn the override. - // In distributed authority mode, we alaways get the override - if (forceOverride || NetworkManager.IsHost || NetworkManager.DistributedAuthorityMode) + // - Host and clients always instantiate the override if one exists. + // - Server instantiates the original prefab unless: + // -- forceOverride is set to true =or= + // -- The prefab has a registered prefab handler, then we let user code determine what to spawn. + // - Distributed authority mode always spawns the override if one exists. + if (forceOverride || NetworkManager.IsClient || NetworkManager.DistributedAuthorityMode || NetworkManager.PrefabHandler.ContainsHandler(networkPrefab.GlobalObjectIdHash)) { networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation); } + else // Under this case, server instantiate the prefab passed in. + { + networkObject = InstantiateNetworkPrefab(networkPrefab.gameObject, networkPrefab.GlobalObjectIdHash, position, rotation); + } + if (networkObject == null) { Debug.LogError($"Failed to instantiate and spawn {networkPrefab.name}!"); @@ -824,16 +832,37 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow else { // Create prefab instance while applying any pre-assigned position and rotation values - networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent(); - networkObject.transform.position = position ?? networkObject.transform.position; - networkObject.transform.rotation = rotation ?? networkObject.transform.rotation; - networkObject.NetworkManagerOwner = NetworkManager; - networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash; + networkObject = InstantiateNetworkPrefab(networkPrefabReference, globalObjectIdHash, position, rotation); } } return networkObject; } + /// + /// Instantiates a network prefab instance, assigns the base prefab , positions, and orients + /// the instance. + /// !!! Should only be invoked by unless used by an integration test !!! + /// + /// + /// should be the base prefab value and not the + /// overrided value. + /// (Can be used for integration testing) + /// + /// prefab to instantiate + /// of the base prefab instance + /// conditional position in place of the network prefab's default position + /// conditional rotation in place of the network prefab's default rotation + /// the instance of the + internal NetworkObject InstantiateNetworkPrefab(GameObject networkPrefab, uint prefabGlobalObjectIdHash, Vector3? position, Quaternion? rotation) + { + var networkObject = UnityEngine.Object.Instantiate(networkPrefab).GetComponent(); + networkObject.transform.position = position ?? networkObject.transform.position; + networkObject.transform.rotation = rotation ?? networkObject.transform.rotation; + networkObject.NetworkManagerOwner = NetworkManager; + networkObject.PrefabGlobalObjectIdHash = prefabGlobalObjectIdHash; + return networkObject; + } + /// /// Creates a local NetowrkObject to be spawned. /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index 8758db8629..1b4e09426a 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -559,6 +559,29 @@ public static void MakeNetworkObjectTestPrefab(NetworkObject networkObject, uint } } + /// + /// Creates a to be used with integration testing + /// + /// namr of the object + /// owner of the object + /// when true, the instance is automatically migrated into the DDOL + /// + internal static GameObject CreateNetworkObject(string baseName, NetworkManager owner, bool moveToDDOL = false) + { + var gameObject = new GameObject + { + name = baseName + }; + var networkObject = gameObject.AddComponent(); + networkObject.NetworkManagerOwner = owner; + MakeNetworkObjectTestPrefab(networkObject); + if (moveToDDOL) + { + Object.DontDestroyOnLoad(gameObject); + } + return gameObject; + } + public static GameObject CreateNetworkObjectPrefab(string baseName, NetworkManager server, params NetworkManager[] clients) { void AddNetworkPrefab(NetworkConfig config, NetworkPrefab prefab) @@ -570,13 +593,7 @@ void AddNetworkPrefab(NetworkConfig config, NetworkPrefab prefab) Assert.IsNotNull(server, prefabCreateAssertError); Assert.IsFalse(server.IsListening, prefabCreateAssertError); - var gameObject = new GameObject - { - name = baseName - }; - var networkObject = gameObject.AddComponent(); - networkObject.NetworkManagerOwner = server; - MakeNetworkObjectTestPrefab(networkObject); + var gameObject = CreateNetworkObject(baseName, server); var networkPrefab = new NetworkPrefab() { Prefab = gameObject }; // We could refactor this test framework to share a NetworkPrefabList instance, but at this point it's diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs.meta new file mode 100644 index 0000000000..588c42fb5b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 83133d71f2112db45ba626157c46e7f8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/AddNetworkPrefabTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs similarity index 97% rename from com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs rename to com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index 2dc5f6001d..c66c843abf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -85,9 +85,14 @@ public void NetworkConfigInvalidNetworkPrefabTest() private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject"; [Test] - public void NetworkPrefabHandlerClass() + public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) { - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); + var networkConfig = new NetworkConfig() + { + NetworkTopology = distributedAuthority ? NetworkTopologyTypes.DistributedAuthority : NetworkTopologyTypes.ClientServer, + }; + + Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, networkConfig: networkConfig)); var testPrefabObjectName = k_PrefabObjectName; Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(testPrefabObjectName); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Tests/Runtime/NetworkPrefabHandlerTests.cs.meta rename to com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs new file mode 100644 index 0000000000..b7d86e3356 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs @@ -0,0 +1,268 @@ +using System.Collections; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// Integration test that validates spawning instances of s with overrides and + /// registered overrides. + /// + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + internal class NetworkPrefabOverrideTests : NetcodeIntegrationTest + { + private const string k_PrefabRootName = "PrefabObj"; + protected override int NumberOfClients => 2; + + private NetworkPrefab m_ClientSidePlayerPrefab; + private NetworkPrefab m_PrefabOverride; + + public NetworkPrefabOverrideTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + + /// + /// Prefab override handler that will instantiate the ServerSideInstance (m_PlayerPrefab) only on server instances + /// and will spawn the ClientSideInstance (m_ClientSidePlayerPrefab.Prefab) only on clients and/or a host. + /// + public class TestPrefabOverrideHandler : MonoBehaviour, INetworkPrefabInstanceHandler + { + public GameObject ServerSideInstance; + public GameObject ClientSideInstance; + private NetworkManager m_NetworkManager; + + private void Start() + { + m_NetworkManager = GetComponent(); + m_NetworkManager.PrefabHandler.AddHandler(ServerSideInstance, this); + } + + private void OnDestroy() + { + if (m_NetworkManager != null && m_NetworkManager.PrefabHandler != null) + { + m_NetworkManager.PrefabHandler.RemoveHandler(ServerSideInstance); + } + } + + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + var instance = m_NetworkManager.IsClient ? Instantiate(ClientSideInstance) : Instantiate(ServerSideInstance); + return instance.GetComponent(); + } + + public void Destroy(NetworkObject networkObject) + { + Object.Destroy(networkObject); + } + } + + /// + /// Mock component for testing that the client-side player is using the right + /// network prefab. + /// + public class ClientSideOnlyComponent : MonoBehaviour + { + + } + + /// + /// When we create the player prefab, make a modified instance that will be used + /// with the . + /// + protected override void OnCreatePlayerPrefab() + { + var clientPlayer = Object.Instantiate(m_PlayerPrefab); + clientPlayer.AddComponent(); + Object.DontDestroyOnLoad(clientPlayer); + m_ClientSidePlayerPrefab = new NetworkPrefab() + { + Prefab = clientPlayer, + }; + + base.OnCreatePlayerPrefab(); + } + + /// + /// Add the additional s and s to + /// all instances. + /// + protected override void OnServerAndClientsCreated() + { + // Create a NetworkPrefab with an override + var basePrefab = NetcodeIntegrationTestHelpers.CreateNetworkObject($"{k_PrefabRootName}-base", m_ServerNetworkManager, true); + var targetPrefab = NetcodeIntegrationTestHelpers.CreateNetworkObject($"{k_PrefabRootName}-over", m_ServerNetworkManager, true); + m_PrefabOverride = new NetworkPrefab() + { + Prefab = basePrefab, + Override = NetworkPrefabOverride.Prefab, + SourcePrefabToOverride = basePrefab, + OverridingTargetPrefab = targetPrefab, + }; + + // Add the prefab override handler for instance specific player prefabs to the server side + var playerPrefabOverrideHandler = m_ServerNetworkManager.gameObject.AddComponent(); + playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab; + playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab; + + // Add the NetworkPrefab with override + m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride); + // Add the client player prefab that will be used on clients (and the host) + m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab); + + foreach (var networkManager in m_ClientNetworkManagers) + { + // Add the prefab override handler for instance specific player prefabs to the client side + playerPrefabOverrideHandler = networkManager.gameObject.AddComponent(); + playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab; + playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab; + + // Add the NetworkPrefab with override + networkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride); + // Add the client player prefab that will be used on clients (and the host) + networkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab); + } + + m_PrefabOverride.Prefab.GetComponent().IsSceneObject = false; + m_PrefabOverride.SourcePrefabToOverride.GetComponent().IsSceneObject = false; + m_PrefabOverride.OverridingTargetPrefab.GetComponent().IsSceneObject = false; + m_ClientSidePlayerPrefab.Prefab.GetComponent().IsSceneObject = false; + + base.OnServerAndClientsCreated(); + } + + protected override IEnumerator OnTearDown() + { + if (m_PrefabOverride != null) + { + if (m_PrefabOverride.SourcePrefabToOverride) + { + Object.Destroy(m_PrefabOverride.SourcePrefabToOverride); + } + + if (m_PrefabOverride.OverridingTargetPrefab) + { + Object.Destroy(m_PrefabOverride.OverridingTargetPrefab); + } + } + + if (m_ClientSidePlayerPrefab != null) + { + if (m_ClientSidePlayerPrefab.Prefab) + { + Object.Destroy(m_ClientSidePlayerPrefab.Prefab); + } + } + m_ClientSidePlayerPrefab = null; + m_PrefabOverride = null; + + yield return base.OnTearDown(); + } + + + private GameObject GetPlayerNetworkPrefabObject(NetworkManager networkManager) + { + return networkManager.IsClient ? m_ClientSidePlayerPrefab.Prefab : m_PlayerPrefab; + } + + [UnityTest] + public IEnumerator PrefabOverrideTests() + { + var prefabNetworkObject = (NetworkObject)null; + var spawnedGlobalObjectId = (uint)0; + + var networkManagers = m_ClientNetworkManagers.ToList(); + if (m_UseHost) + { + networkManagers.Insert(0, m_ServerNetworkManager); + } + else + { + // If running as just a server, validate that all player prefab clone instances are the server side version + prefabNetworkObject = GetPlayerNetworkPrefabObject(m_ServerNetworkManager).GetComponent(); + foreach (var playerEntry in m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId]) + { + spawnedGlobalObjectId = playerEntry.Value.GlobalObjectIdHash; + Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash == spawnedGlobalObjectId, $"Server-Side {playerEntry.Value.name} was spawned as prefab ({spawnedGlobalObjectId}) but we expected ({prefabNetworkObject.GlobalObjectIdHash})!"); + } + } + + // Validates prefab overrides via the NetworkPrefabHandler. + // Validate the player prefab instance clones relative to all NetworkManagers. + foreach (var networkManager in networkManagers) + { + // Get the expected player prefab to be spawned based on the NetworkManager + prefabNetworkObject = GetPlayerNetworkPrefabObject(networkManager).GetComponent(); + if (networkManager.IsClient) + { + spawnedGlobalObjectId = networkManager.LocalClient.PlayerObject.GlobalObjectIdHash; + Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash == spawnedGlobalObjectId, $"{networkManager.name} spawned player prefab ({spawnedGlobalObjectId}) did not match the expected one ({prefabNetworkObject.GlobalObjectIdHash})!"); + } + + foreach (var playerEntry in m_PlayerNetworkObjects[networkManager.LocalClientId]) + { + // We already checked our locally spawned player prefab above + if (playerEntry.Key == networkManager.LocalClientId) + { + continue; + } + spawnedGlobalObjectId = playerEntry.Value.GlobalObjectIdHash; + Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash == spawnedGlobalObjectId, $"Client-{networkManager.LocalClientId} clone of {playerEntry.Value.name} was spawned as prefab ({spawnedGlobalObjectId}) but we expected ({prefabNetworkObject.GlobalObjectIdHash})!"); + } + } + + // Validates prefab overrides via NetworkPrefab configuration. + var spawnedInstance = (NetworkObject)null; + var networkManagerOwner = m_ServerNetworkManager; + + if (m_DistributedAuthority) + { + networkManagerOwner = m_ClientNetworkManagers[0]; + } + // Clients and Host will spawn the OverridingTargetPrefab while a dedicated server will spawn the SourcePrefabToOverride + var expectedServerGlobalObjectIdHash = networkManagerOwner.IsClient ? m_PrefabOverride.OverridingTargetPrefab.GetComponent().GlobalObjectIdHash : m_PrefabOverride.SourcePrefabToOverride.GetComponent().GlobalObjectIdHash; + var expectedClientGlobalObjectIdHash = m_PrefabOverride.OverridingTargetPrefab.GetComponent().GlobalObjectIdHash; + + spawnedInstance = NetworkObject.InstantiateAndSpawn(m_PrefabOverride.SourcePrefabToOverride, networkManagerOwner, networkManagerOwner.LocalClientId); + var builder = new StringBuilder(); + bool ObjectSpawnedOnAllNetworkMangers() + { + builder.Clear(); + if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(spawnedInstance.NetworkObjectId)) + { + builder.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} failed to spawn {spawnedInstance.name}-{spawnedInstance.NetworkObjectId}!"); + return false; + } + var instanceGID = m_ServerNetworkManager.SpawnManager.SpawnedObjects[spawnedInstance.NetworkObjectId].GlobalObjectIdHash; + if (instanceGID != expectedServerGlobalObjectIdHash) + { + builder.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} instance {spawnedInstance.name}-{spawnedInstance.NetworkObjectId} GID is {instanceGID} but was expected to be {expectedServerGlobalObjectIdHash}!"); + return false; + } + + foreach (var networkManger in m_ClientNetworkManagers) + { + if (!networkManger.SpawnManager.SpawnedObjects.ContainsKey(spawnedInstance.NetworkObjectId)) + { + builder.AppendLine($"Client-{networkManger.LocalClientId} failed to spawn {spawnedInstance.name}-{spawnedInstance.NetworkObjectId}!"); + return false; + } + instanceGID = networkManger.SpawnManager.SpawnedObjects[spawnedInstance.NetworkObjectId].GlobalObjectIdHash; + if (instanceGID != expectedClientGlobalObjectIdHash) + { + builder.AppendLine($"Client-{networkManger.LocalClientId} instance {spawnedInstance.name}-{spawnedInstance.NetworkObjectId} GID is {instanceGID} but was expected to be {expectedClientGlobalObjectIdHash}!"); + return false; + } + } + return true; + } + + yield return WaitForConditionOrTimeOut(ObjectSpawnedOnAllNetworkMangers); + AssertOnTimeout($"The spawned prefab override validation failed!\n {builder}"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs.meta new file mode 100644 index 0000000000..e0120dbf5c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c5e27e24bc5f783458d6a45585308f87 \ No newline at end of file From 5e38c31a6e3ddaf87205fd1e7bb20f2ac7409451 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 10 Dec 2024 09:17:55 -0600 Subject: [PATCH 140/236] fix: prefab incontext or inIsolation edit mode globalobjectidhash incorrect when using MPPM (#3162) * fix This fixes the issue with the GlobalObjectId value being incorrect when entering into play mode while still in prefab edit mode (InContext or InIsolation). * fix Minor fix to how we shutdown NetworkManager when exiting playmode to assure we follow the same steps as we do when the application quits. * fix InSceneObject needed to be checked for having a value before checking for the value. * update Removed legacy methods used to detect when in edit mode. Updated comments. * update Adding changelog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 3 +- .../Runtime/Core/NetworkObject.cs | 203 +++++++++++------- 3 files changed, 124 insertions(+), 83 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 228a4217ea..99197d550f 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where the `NetworkObjectIdHash` value could be incorrect when entering play mode while still in prefab edit mode with pending changes and using MPPM. (#3162) - Fixed issue where a sever only `NetworkManager` instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160) - Fixed issue where only the session owner (as opposed to all clients) would handle spawning prefab overrides properly when using a distributed authority network topology. (#3160) - Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index eacda7e87d..6a7d71f159 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1023,8 +1023,7 @@ private void ModeChanged(PlayModeStateChange change) { if (IsListening && change == PlayModeStateChange.ExitingPlayMode) { - // Make sure we are not holding onto anything in case domain reload is disabled - ShutdownInternal(); + OnApplicationQuit(); } } #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 501cda138d..5d94f4fd3c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -102,6 +102,16 @@ public uint PrefabIdHash private const int k_SceneObjectType = 2; private const int k_SourceAssetObjectType = 3; + // Used to track any InContext or InIsolation prefab being edited. + private static PrefabStage s_PrefabStage; + // The network prefab asset that the edit mode scene has created an instance of (s_PrefabInstance). + private static NetworkObject s_PrefabAsset; + // The InContext or InIsolation edit mode network prefab scene instance of the prefab asset (s_PrefabAsset). + private static NetworkObject s_PrefabInstance; + + private static bool s_DebugPrefabIdGeneration; + + [ContextMenu("Refresh In-Scene Prefab Instances")] internal void RefreshAllPrefabInstances() { @@ -134,25 +144,119 @@ internal void RefreshAllPrefabInstances() NetworkObjectRefreshTool.ProcessScenes(); } + /// + /// Register for opened and closing event notifications. + /// + [InitializeOnLoadMethod] + private static void OnApplicationStart() + { + PrefabStage.prefabStageOpened -= PrefabStageOpened; + PrefabStage.prefabStageOpened += PrefabStageOpened; + PrefabStage.prefabStageClosing -= PrefabStageClosing; + PrefabStage.prefabStageClosing += PrefabStageClosing; + } + + private static void PrefabStageClosing(PrefabStage prefabStage) + { + // If domain reloading is enabled, then this will be null when we return from playmode. + if (s_PrefabStage == null) + { + // Determine if we have a network prefab opened in edit mode or not. + CheckPrefabStage(prefabStage); + } + + s_PrefabStage = null; + s_PrefabInstance = null; + s_PrefabAsset = null; + } + + private static void PrefabStageOpened(PrefabStage prefabStage) + { + // Determine if we have a network prefab opened in edit mode or not. + CheckPrefabStage(prefabStage); + } + + /// + /// Determines if we have opened a network prefab in edit mode (InContext or InIsolation) + /// + /// + /// InContext: Typically means a are in prefab edit mode for an in-scene placed network prefab instance. + /// (currently no such thing as a network prefab with nested network prefab instances) + /// + /// InIsolation: Typically means we are in prefb edit mode for a prefab asset. + /// + /// + private static void CheckPrefabStage(PrefabStage prefabStage) + { + s_PrefabStage = prefabStage; + s_PrefabInstance = prefabStage.prefabContentsRoot?.GetComponent(); + if (s_PrefabInstance) + { + // We acquire the source prefab that the prefab edit mode scene instance was instantiated from differently for InContext than InSolation. + if (s_PrefabStage.mode == PrefabStage.Mode.InContext && s_PrefabStage.openedFromInstanceRoot != null) + { + // This is needed to handle the scenario where a user completely loads a new scene while in an InContext prefab edit mode. + try + { + s_PrefabAsset = s_PrefabStage.openedFromInstanceRoot?.GetComponent(); + } + catch + { + s_PrefabAsset = null; + } + } + else + { + // When editing in InIsolation mode, load the original prefab asset from the provided path. + s_PrefabAsset = AssetDatabase.LoadAssetAtPath(s_PrefabStage.assetPath); + } + + if (s_PrefabInstance.GlobalObjectIdHash != s_PrefabAsset.GlobalObjectIdHash) + { + s_PrefabInstance.GlobalObjectIdHash = s_PrefabAsset.GlobalObjectIdHash; + // For InContext mode, we don't want to record these modifications (the in-scene GlobalObjectIdHash is serialized with the scene). + if (s_PrefabStage.mode == PrefabStage.Mode.InIsolation) + { + PrefabUtility.RecordPrefabInstancePropertyModifications(s_PrefabAsset); + } + } + } + else + { + s_PrefabStage = null; + s_PrefabInstance = null; + s_PrefabAsset = null; + } + } + + /// + /// GlobalObjectIdHash values are generated during validation. + /// internal void OnValidate() { - // do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode + // Always exit early if we are in prefab edit mode and this instance is the + // prefab instance within the InContext or InIsolation edit scene. + if (s_PrefabInstance == this) + { + return; + } + + // Do not regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in play mode. if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name)) { return; } - // do NOT regenerate GlobalObjectIdHash if Editor is transitioning into or out of PlayMode + // Do not regenerate GlobalObjectIdHash if Editor is transitioning into or out of play mode. if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode) { return; } - // Get a global object identifier for this network prefab - var globalId = GetGlobalId(); - + // Get a global object identifier for this network prefab. + var globalId = GlobalObjectId.GetGlobalObjectIdSlow(this); - // if the identifier type is 0, then don't update the GlobalObjectIdHash + // if the identifier type is 0, then don't update the GlobalObjectIdHash. if (globalId.identifierType == k_NullObjectType) { return; @@ -161,47 +265,34 @@ internal void OnValidate() var oldValue = GlobalObjectIdHash; GlobalObjectIdHash = globalId.ToString().Hash32(); - // If the GlobalObjectIdHash value changed, then mark the asset dirty + // Always check for in-scene placed to assure any previous version scene assets with in-scene place NetworkObjects gets updated. + CheckForInScenePlaced(); + + // If the GlobalObjectIdHash value changed, then mark the asset dirty. if (GlobalObjectIdHash != oldValue) { - // Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed) - if (!IsEditingPrefab() && gameObject.scene.name != null && gameObject.scene.name != gameObject.name) + // Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed). + if (IsSceneObject.HasValue && IsSceneObject.Value) { - // Sanity check to make sure this is a scene placed object + // Sanity check to make sure this is a scene placed object. if (globalId.identifierType != k_SceneObjectType) { - // This should never happen, but in the event it does throw and error + // This should never happen, but in the event it does throw and error. Debug.LogError($"[{gameObject.name}] is detected as an in-scene placed object but its identifier is of type {globalId.identifierType}! **Report this error**"); } - // If this is a prefab instance + // If this is a prefab instance, then we want to mark it as having been updated in order for the udpated GlobalObjectIdHash value to be saved. if (PrefabUtility.IsPartOfAnyPrefab(this)) { - // We must invoke this in order for the modifications to get saved with the scene (does not mark scene as dirty) + // We must invoke this in order for the modifications to get saved with the scene (does not mark scene as dirty). PrefabUtility.RecordPrefabInstancePropertyModifications(this); } } - else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it + else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it. { EditorUtility.SetDirty(this); } } - - // Always check for in-scene placed to assure any previous version scene assets with in-scene place NetworkObjects gets updated - CheckForInScenePlaced(); - } - - private bool IsEditingPrefab() - { - // Check if we are directly editing the prefab - var stage = PrefabStageUtility.GetPrefabStage(gameObject); - - // if we are not editing the prefab directly (or a sub-prefab), then return the object identifier - if (stage == null || stage.assetPath == null) - { - return false; - } - return true; } /// @@ -212,13 +303,12 @@ private bool IsEditingPrefab() /// /// This NetworkObject is considered an in-scene placed prefab asset instance if it is: /// - Part of a prefab - /// - Not being directly edited /// - Within a valid scene that is part of the scenes in build list /// (In-scene defined NetworkObjects that are not part of a prefab instance are excluded.) /// private void CheckForInScenePlaced() { - if (PrefabUtility.IsPartOfAnyPrefab(this) && !IsEditingPrefab() && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0) + if (PrefabUtility.IsPartOfAnyPrefab(this) && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0) { var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject); var assetPath = AssetDatabase.GetAssetPath(prefab); @@ -231,55 +321,6 @@ private void CheckForInScenePlaced() IsSceneObject = true; } } - - private GlobalObjectId GetGlobalId() - { - var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this); - - // If not editing a prefab, then just use the generated id - if (!IsEditingPrefab()) - { - return instanceGlobalId; - } - - // If the asset doesn't exist at the given path, then return the object identifier - var prefabStageAssetPath = PrefabStageUtility.GetPrefabStage(gameObject).assetPath; - // If (for some reason) the asset path is null return the generated id - if (prefabStageAssetPath == null) - { - return instanceGlobalId; - } - - var theAsset = AssetDatabase.LoadAssetAtPath(prefabStageAssetPath); - // If there is no asset at that path (for some odd/edge case reason), return the generated id - if (theAsset == null) - { - return instanceGlobalId; - } - - // If we can't get the asset GUID and/or the file identifier, then return the object identifier - if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(theAsset, out var guid, out long localFileId)) - { - return instanceGlobalId; - } - - // Note: If we reached this point, then we are most likely opening a prefab to edit. - // The instanceGlobalId will be constructed as if it is a scene object, however when it - // is serialized its value will be treated as a file asset (the "why" to the below code). - - // Construct an imported asset identifier with the type being a source asset object type - var prefabGlobalIdText = string.Format(k_GlobalIdTemplate, k_SourceAssetObjectType, guid, (ulong)localFileId, 0); - - // If we can't parse the result log an error and return the instanceGlobalId - if (!GlobalObjectId.TryParse(prefabGlobalIdText, out var prefabGlobalId)) - { - Debug.LogError($"[GlobalObjectId Gen] Failed to parse ({prefabGlobalIdText}) returning default ({instanceGlobalId})! ** Please Report This Error **"); - return instanceGlobalId; - } - - // Otherwise, return the constructed identifier for the source prefab asset - return prefabGlobalId; - } #endif // UNITY_EDITOR /// From c69aa97e9b24deb91738703e9d391b423a856a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:30:03 +0100 Subject: [PATCH 141/236] chore: forward port of Wrench from release/1.12.0 branch (#3171) * Added setup necessary for release automation of NGO (#3141) generated files for release automation within Unity org * Corrected setup necessary for release automation of NGO (#3149) * generated files for release automation within Unity org * Corrected pvp profiles check * PVP and Wrench fixes * Updated wrench version to 0.10.30 * Corrected license file * Removed old files not conforming to pvp rules * Corrected typo in changelog * Added samples description to package.json while removing deprecated .samples.json * Inclusion of CI generation files * Pvp fixes for automated release * Changed Samples folder name to Samples~ * Added path field in package.json for samples * Recipes regenerated for NGOv2X * Removed ValidationException file added by accident from 2.0.0 branch --- .yamato/wrench/api-validation-jobs.yml | 60 +++ .yamato/wrench/package-pack-jobs.yml | 36 ++ .yamato/wrench/preview-a-p-v.yml | 366 ++++++++++++++++ .yamato/wrench/promotion-jobs.yml | 172 ++++++++ .yamato/wrench/recipe-regeneration.yml | 30 ++ .yamato/wrench/validation-jobs.yml | 405 ++++++++++++++++++ .yamato/wrench/wrench_config.json | 37 ++ Tools/CI/NGO-recipes.sln | 16 + Tools/CI/NGO.Cookbook.csproj | 14 + Tools/CI/Program.cs | 21 + Tools/CI/Settings/NGOSettings.cs | 34 ++ Tools/CI/global.json | 7 + Tools/CI/nuget.config | 6 + Tools/CI/regenerate.bat | 2 + Tools/CI/regenerate.sh | 2 + com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- com.unity.netcode.gameobjects/Samples.meta | 8 - .../Samples/Bootstrap/.sample.json | 4 - .../{Samples => Samples~}/Bootstrap.meta | 0 .../Bootstrap/Prefabs.meta | 0 .../Bootstrap/Prefabs/BootstrapPlayer.prefab | 0 .../Prefabs/BootstrapPlayer.prefab.meta | 0 .../Bootstrap/Scenes.meta | 0 .../Bootstrap/Scenes/Bootstrap.unity | 0 .../Bootstrap/Scenes/Bootstrap.unity.meta | 0 .../Bootstrap/Scripts.meta | 0 .../Bootstrap/Scripts/Bootstrap.asmdef | 0 .../Bootstrap/Scripts/Bootstrap.asmdef.meta | 0 .../Bootstrap/Scripts/BootstrapManager.cs | 0 .../Scripts/BootstrapManager.cs.meta | 0 .../Bootstrap/Scripts/BootstrapPlayer.cs | 0 .../Bootstrap/Scripts/BootstrapPlayer.cs.meta | 0 .../ValidationExceptions.json | 4 - .../ValidationExceptions.json.meta | 7 - com.unity.netcode.gameobjects/package.json | 7 + 35 files changed, 1216 insertions(+), 24 deletions(-) create mode 100644 .yamato/wrench/api-validation-jobs.yml create mode 100644 .yamato/wrench/package-pack-jobs.yml create mode 100644 .yamato/wrench/preview-a-p-v.yml create mode 100644 .yamato/wrench/promotion-jobs.yml create mode 100644 .yamato/wrench/recipe-regeneration.yml create mode 100644 .yamato/wrench/validation-jobs.yml create mode 100644 .yamato/wrench/wrench_config.json create mode 100644 Tools/CI/NGO-recipes.sln create mode 100644 Tools/CI/NGO.Cookbook.csproj create mode 100644 Tools/CI/Program.cs create mode 100644 Tools/CI/Settings/NGOSettings.cs create mode 100644 Tools/CI/global.json create mode 100644 Tools/CI/nuget.config create mode 100644 Tools/CI/regenerate.bat create mode 100644 Tools/CI/regenerate.sh delete mode 100644 com.unity.netcode.gameobjects/Samples.meta delete mode 100644 com.unity.netcode.gameobjects/Samples/Bootstrap/.sample.json rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Prefabs.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Prefabs/BootstrapPlayer.prefab (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Prefabs/BootstrapPlayer.prefab.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scenes.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scenes/Bootstrap.unity (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scenes/Bootstrap.unity.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scripts.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scripts/Bootstrap.asmdef (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scripts/Bootstrap.asmdef.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scripts/BootstrapManager.cs (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scripts/BootstrapManager.cs.meta (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scripts/BootstrapPlayer.cs (100%) rename com.unity.netcode.gameobjects/{Samples => Samples~}/Bootstrap/Scripts/BootstrapPlayer.cs.meta (100%) delete mode 100644 com.unity.netcode.gameobjects/ValidationExceptions.json delete mode 100644 com.unity.netcode.gameobjects/ValidationExceptions.json.meta diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml new file mode 100644 index 0000000000..03dbad8669 --- /dev/null +++ b/.yamato/wrench/api-validation-jobs.yml @@ -0,0 +1,60 @@ +# Auto-generated by Recipe Engine, do not modify manually. +# This job is generated by the wrench recipe engine module. + +# upm-ci validation tests for API Validation - netcode.gameobjects - 6000.0 - windows (6000.0 - Windows). +api_validation_-_netcode_gameobjects_-_6000_0_-_windows: + name: API Validation - netcode.gameobjects - 6000.0 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.0 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/PackageJsonCondersor.py + timeout: 1 + retries: 0 + - command: upm-ci package test -u .Editor --package-path com.unity.netcode.gameobjects --type vetting-tests || exit 0 + timeout: 30 + retries: 0 + - command: python PythonScripts/parse_upm_ci_results.py --package-path=com.unity.netcode.gameobjects + timeout: 2 + retries: 0 + after: + - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + browsable: onNonSuccess + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + triggers: + expression: push.branch match "^release/.*" + cancel_old_ci: true + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml new file mode 100644 index 0000000000..0dfb1e44cf --- /dev/null +++ b/.yamato/wrench/package-pack-jobs.yml @@ -0,0 +1,36 @@ +# Auto-generated by Recipe Engine, do not modify manually. +# This job is generated by the wrench recipe engine module. + +# Pack and Sign Netcode for GameObjects +package_pack_-_netcode_gameobjects: + name: Package Pack - netcode.gameobjects + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: upm-ci package pack --package-path com.unity.netcode.gameobjects + - command: cp upm-ci~/packages/packages.json upm-ci~/packages/com.unity.netcode.gameobjects_packages.json + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + packages: + paths: + - upm-ci~/packages/**/* + variables: + UPMCI_ACK_LARGE_PACKAGE: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml new file mode 100644 index 0000000000..9ad7ec7b24 --- /dev/null +++ b/.yamato/wrench/preview-a-p-v.yml @@ -0,0 +1,366 @@ +# Auto-generated by Recipe Engine, do not modify manually. +# This job is generated by the wrench recipe engine module. + +# Parent Preview APV Job. +all_preview_apv_jobs: + name: All Preview APV Jobs + dependencies: + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_0_-_macos + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_0_-_ubuntu + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_0_-_windows + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_1_-_macos + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_1_-_ubuntu + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_1_-_windows + triggers: + expression: push.branch match "^release/.*" + cancel_old_ci: true + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + +# Functional tests for dependents found in the latest 6000.0 manifest (MacOS). +preview_apv_-_6000_0_-_macos: + name: Preview APV - 6000.0 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.0 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + +# Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). +preview_apv_-_6000_0_-_ubuntu: + name: Preview APV - 6000.0 - ubuntu + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.0 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + +# Functional tests for dependents found in the latest 6000.0 manifest (Windows). +preview_apv_-_6000_0_-_windows: + name: Preview APV - 6000.0 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.0 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.0 --wrench-config=.yamato/wrench/wrench_config.json + after: + - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + interpreter: powershell + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + +# Functional tests for dependents found in the latest 6000.1 manifest (MacOS). +preview_apv_-_6000_1_-_macos: + name: Preview APV - 6000.1 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.1 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.1 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + +# Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). +preview_apv_-_6000_1_-_ubuntu: + name: Preview APV - 6000.1 - ubuntu + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.1 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.1 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + +# Functional tests for dependents found in the latest 6000.1 manifest (Windows). +preview_apv_-_6000_1_-_windows: + name: Preview APV - 6000.1 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.1 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.1 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.1 --wrench-config=.yamato/wrench/wrench_config.json + after: + - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + interpreter: powershell + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml new file mode 100644 index 0000000000..d3f5d202d2 --- /dev/null +++ b/.yamato/wrench/promotion-jobs.yml @@ -0,0 +1,172 @@ +# Auto-generated by Recipe Engine, do not modify manually. +# This job is generated by the wrench recipe engine module. + +# Publish Dry Run for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm +publish_dry_run_netcode_gameobjects: + name: Publish Dry Run netcode.gameobjects + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/report_valid_editors.py + - command: python PythonScripts/ignore_existing_package_failure.py + - command: python PythonScripts/run_publish_if_any_package_left.py --dry-run + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + logs: + paths: + - results/UTR/**/* + browsable: onNonSuccess + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_macos + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.0-macos + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.0-macos + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_ubuntu + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.0-ubuntu + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.0-ubuntu + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_windows + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.0-windows + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.0-windows + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_macos + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.1-macos + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.1-macos + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_ubuntu + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.1-ubuntu + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.1-ubuntu + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_windows + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.1-windows + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.1-windows + unzip: true + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + triggers: + expression: push.branch match "^release/.*" + cancel_old_ci: true + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + +# Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm +publish_netcode_gameobjects: + name: Publish netcode.gameobjects + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/report_valid_editors.py + - command: python PythonScripts/ignore_existing_package_failure.py + - command: python PythonScripts/run_publish_if_any_package_left.py + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + logs: + paths: + - results/UTR/**/* + browsable: onNonSuccess + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_macos + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.0-macos + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.0-macos + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_ubuntu + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.0-ubuntu + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.0-ubuntu + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_windows + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.0-windows + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.0-windows + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_macos + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.1-macos + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.1-macos + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_ubuntu + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.1-ubuntu + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.1-ubuntu + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_windows + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.1-windows + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.1-windows + unzip: true + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml new file mode 100644 index 0000000000..03a579082d --- /dev/null +++ b/.yamato/wrench/recipe-regeneration.yml @@ -0,0 +1,30 @@ +# Auto-generated by Recipe Engine, do not modify manually. +# This job is generated by the wrench recipe engine module. + +# Test that Generated Wrench Jobs are up to date. +test_-_wrench_jobs_up_to_date: + name: Test - Wrench Jobs up to date + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: dotnet run --project Tools\CI\NGO.Cookbook.csproj + - command: |- + if [ -n "$(git status --porcelain)" ]; then + git status + echo "Your repo is not clean - diff output:" + git diff + echo "You must run recipe generation after updating recipes to update the generated YAML!" + echo "Run 'dotnet run --project Tools\CI\NGO.Cookbook.csproj' from the root of your repository to regenerate all job definitions created by wrench." + exit 1 + fi + variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + triggers: + expression: push.branch match "^release/.*" + cancel_old_ci: true + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml new file mode 100644 index 0000000000..db48d0b2bc --- /dev/null +++ b/.yamato/wrench/validation-jobs.yml @@ -0,0 +1,405 @@ +# Auto-generated by Recipe Engine, do not modify manually. +# This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.0 - macos (6000.0 - MacOS). +validate_-_netcode_gameobjects_-_6000_0_-_macos: + name: Validate - netcode.gameobjects - 6000.0 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.0 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.0 - ubuntu (6000.0 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: + name: Validate - netcode.gameobjects - 6000.0 - ubuntu + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.0 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.0 - windows (6000.0 - Windows). +validate_-_netcode_gameobjects_-_6000_0_-_windows: + name: Validate - netcode.gameobjects - 6000.0 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.0 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.1 - macos (6000.1 - MacOS). +validate_-_netcode_gameobjects_-_6000_1_-_macos: + name: Validate - netcode.gameobjects - 6000.1 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.1 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.1 - ubuntu (6000.1 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: + name: Validate - netcode.gameobjects - 6000.1 - ubuntu + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.1 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.1 - windows (6000.1 - Windows). +validate_-_netcode_gameobjects_-_6000_1_-_windows: + name: Validate - netcode.gameobjects - 6000.1 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.1 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.31.0 + labels: + - Packages:netcode.gameobjects + diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json new file mode 100644 index 0000000000..2ef87583c3 --- /dev/null +++ b/.yamato/wrench/wrench_config.json @@ -0,0 +1,37 @@ +{ + "schema_version": 0.7, + "packages": { + "com.unity.netcode.gameobjects": { + "directory": "com.unity.netcode.gameobjects/", + "prePackCommands": [], + "preTestCommands": { + "MacOS": [], + "Ubuntu": [], + "Windows": [] + }, + "InternalOnly": false, + "NeverPublish": false, + "MaxEditorVersion": "", + "coverageEnabled": false, + "coverageCommands": [ + "generateAdditionalMetrics;generateHtmlReport;assemblyFilters:ASSEMBLY_NAME;pathReplacePatterns:@*,,**/PackageCache/,;sourcePaths:YAMATO_SOURCE_DIR/Packages;" + ], + "dependantsToIgnoreInPreviewApv": {} + } + }, + "releasing_packages": [ + "com.unity.netcode.gameobjects" + ], + "jobs_to_monitor": { + "com.unity.netcode.gameobjects": [ + ".yamato/wrench/api-validation-jobs.yml#api_validation_-_netcode_gameobjects_-_6000_0_-_windows", + ".yamato/wrench/preview-a-p-v.yml#all_preview_apv_jobs", + ".yamato/wrench/promotion-jobs.yml#publish_dry_run_netcode_gameobjects" + ] + }, + "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", + "branch_pattern": "ReleaseSlash", + "wrench_version": "0.10.31.0", + "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", + "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" +} \ No newline at end of file diff --git a/Tools/CI/NGO-recipes.sln b/Tools/CI/NGO-recipes.sln new file mode 100644 index 0000000000..f4e6215128 --- /dev/null +++ b/Tools/CI/NGO-recipes.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NGO.Cookbook", "NGO.Cookbook.csproj", "{A5A71435-C891-4C78-989C-2713DAA7B3B8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A5A71435-C891-4C78-989C-2713DAA7B3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5A71435-C891-4C78-989C-2713DAA7B3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5A71435-C891-4C78-989C-2713DAA7B3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5A71435-C891-4C78-989C-2713DAA7B3B8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj new file mode 100644 index 0000000000..f5b5a6e439 --- /dev/null +++ b/Tools/CI/NGO.Cookbook.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/Tools/CI/Program.cs b/Tools/CI/Program.cs new file mode 100644 index 0000000000..176215bdff --- /dev/null +++ b/Tools/CI/Program.cs @@ -0,0 +1,21 @@ +using NGO.Cookbook.Settings; +using RecipeEngine; +using RecipeEngine.Modules.Wrench.Helpers; + + +// ReSharper disable once CheckNamespace +public static class Program +{ + public static int Main(string[] args) + { + var settings = new NGOSettings(); + + // ReSharper disable once UnusedVariable + var engine = EngineFactory + .Create() + .ScanAll() + .WithWrenchModule(settings.Wrench) + .GenerateAsync().Result; + return engine; + } +} diff --git a/Tools/CI/Settings/NGOSettings.cs b/Tools/CI/Settings/NGOSettings.cs new file mode 100644 index 0000000000..ebd24769c0 --- /dev/null +++ b/Tools/CI/Settings/NGOSettings.cs @@ -0,0 +1,34 @@ +using RecipeEngine.Api.Settings; +using RecipeEngine.Modules.Wrench.Models; +using RecipeEngine.Modules.Wrench.Settings; + +namespace NGO.Cookbook.Settings; + +public class NGOSettings : AnnotatedSettingsBase +{ + // Path from the root of the repository where packages are located. + readonly string[] packagesRootPaths = {"."}; + + // update this to list all packages in this repo that you want to release. + Dictionary PackageOptions = new() + { + { + "com.unity.netcode.gameobjects", + new PackageOptions() { ReleaseOptions = new ReleaseOptions() { IsReleasing = true } } + } + }; + + public NGOSettings() + { + Wrench = new WrenchSettings( + packagesRootPaths, + PackageOptions, + false, + false, + @"Tools\CI\NGO.Cookbook.csproj"); // There should be fix soon and there should be no need of specifying the path + + Wrench.PvpProfilesToCheck = new HashSet() { "supported" }; + } + + public WrenchSettings Wrench { get; private set; } +} diff --git a/Tools/CI/global.json b/Tools/CI/global.json new file mode 100644 index 0000000000..3cb4f9622d --- /dev/null +++ b/Tools/CI/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "7.0.0", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} diff --git a/Tools/CI/nuget.config b/Tools/CI/nuget.config new file mode 100644 index 0000000000..de6af57e8d --- /dev/null +++ b/Tools/CI/nuget.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Tools/CI/regenerate.bat b/Tools/CI/regenerate.bat new file mode 100644 index 0000000000..33c9c9fb12 --- /dev/null +++ b/Tools/CI/regenerate.bat @@ -0,0 +1,2 @@ +cd %~dp0../../ +dotnet run --project Tools\CI\NGO.Cookbook.csproj \ No newline at end of file diff --git a/Tools/CI/regenerate.sh b/Tools/CI/regenerate.sh new file mode 100644 index 0000000000..ef2be6b8d8 --- /dev/null +++ b/Tools/CI/regenerate.sh @@ -0,0 +1,2 @@ +cd $(dirname "$0")/../../ +dotnet run --project Tools\CI\NGO.Cookbook.csproj \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 99197d550f..2d16bbc30e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -470,7 +470,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where invalid endpoint addresses were not being detected and returning false from NGO UnityTransport. (#2496) - Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495) -## Changed +### Changed - Adding network prefabs before NetworkManager initialization is now supported. (#2565) - Connecting clients being synchronized now switch to the server's active scene before spawning and synchronizing NetworkObjects. (#2532) diff --git a/com.unity.netcode.gameobjects/Samples.meta b/com.unity.netcode.gameobjects/Samples.meta deleted file mode 100644 index 5a5e3b286a..0000000000 --- a/com.unity.netcode.gameobjects/Samples.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d48e3f2a8be084f8aae6470ef87063d9 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/.sample.json b/com.unity.netcode.gameobjects/Samples/Bootstrap/.sample.json deleted file mode 100644 index f62cf3082d..0000000000 --- a/com.unity.netcode.gameobjects/Samples/Bootstrap/.sample.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "Bootstrap", - "description": "A lightweight sample to get started" -} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Prefabs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Prefabs.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Prefabs/BootstrapPlayer.prefab similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Prefabs/BootstrapPlayer.prefab diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Prefabs/BootstrapPlayer.prefab.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Prefabs/BootstrapPlayer.prefab.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Prefabs/BootstrapPlayer.prefab.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scenes.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scenes.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scenes.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scenes.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scenes/Bootstrap.unity b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scenes/Bootstrap.unity similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scenes/Bootstrap.unity rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scenes/Bootstrap.unity diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scenes/Bootstrap.unity.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scenes/Bootstrap.unity.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scenes/Bootstrap.unity.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scenes/Bootstrap.unity.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/Bootstrap.asmdef b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/Bootstrap.asmdef rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/Bootstrap.asmdef.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/Bootstrap.asmdef.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapManager.cs b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapManager.cs similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapManager.cs rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapManager.cs diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapManager.cs.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapManager.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapManager.cs.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapManager.cs.meta diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapPlayer.cs b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapPlayer.cs rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs diff --git a/com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapPlayer.cs.meta b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Samples/Bootstrap/Scripts/BootstrapPlayer.cs.meta rename to com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs.meta diff --git a/com.unity.netcode.gameobjects/ValidationExceptions.json b/com.unity.netcode.gameobjects/ValidationExceptions.json deleted file mode 100644 index 9128292b92..0000000000 --- a/com.unity.netcode.gameobjects/ValidationExceptions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ErrorExceptions": [], - "WarningExceptions": [] -} diff --git a/com.unity.netcode.gameobjects/ValidationExceptions.json.meta b/com.unity.netcode.gameobjects/ValidationExceptions.json.meta deleted file mode 100644 index 3316cf20bd..0000000000 --- a/com.unity.netcode.gameobjects/ValidationExceptions.json.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 2a43005be301c9043aab7034757d4868 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index a632de0172..edbce0938e 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -7,5 +7,12 @@ "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.transport": "2.3.0" + }, + "samples": [ + { + "displayName": "Bootstrap", + "description": "A lightweight sample to get started", + "path": "Samples~/Bootstrap" } + ] } From b7ee6ef8e9dd5a60b694a98b37268a117d16c5ce Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 12 Dec 2024 08:55:40 -0600 Subject: [PATCH 142/236] update updating package --- com.unity.netcode.gameobjects/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index edbce0938e..810105020d 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,11 +2,11 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.1.1", - "unity": "6000.0", + "version": "2.2.0", + "unity": "6000.1", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.3.0" + "com.unity.transport": "2.4.0" }, "samples": [ { From 4a77fb20720366ada79031f1fa1706ee2e1e7aa5 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 12 Dec 2024 08:55:54 -0600 Subject: [PATCH 143/236] update updating changelog --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2d16bbc30e..229b506e5c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,7 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -[Unreleased] + +[2.2.0 2024-12-12] ### Added From 088755ea3a5c6ad2fa000c97b52833857badc1a4 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 12 Dec 2024 09:23:25 -0600 Subject: [PATCH 144/236] update moving minimum editor back down to 6000.0 --- com.unity.netcode.gameobjects/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 810105020d..c20e366c3c 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -3,7 +3,7 @@ "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", "version": "2.2.0", - "unity": "6000.1", + "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.transport": "2.4.0" From 004a2206b27dbeb30c01bbe6204f96987053b8ec Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 12 Dec 2024 10:54:05 -0600 Subject: [PATCH 145/236] fix Fixing the changelog version goof of mine. Making the license copyright just a year and not year range. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- com.unity.netcode.gameobjects/LICENSE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 229b506e5c..3b068b5c21 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -[2.2.0 2024-12-12] +## [2.2.0] 2024-12-12 ### Added diff --git a/com.unity.netcode.gameobjects/LICENSE.md b/com.unity.netcode.gameobjects/LICENSE.md index 031978c204..6e26290e2d 100644 --- a/com.unity.netcode.gameobjects/LICENSE.md +++ b/com.unity.netcode.gameobjects/LICENSE.md @@ -1,6 +1,6 @@ Unity Companion License (UCL License) -com.unity.netcode.gameobjects copyright © 2021-2024 Unity Technologies +com.unity.netcode.gameobjects copyright © 2024 Unity Technologies Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license). Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. From 64195d57a8e6a154e4620428eef2099e67f7ff39 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 12 Dec 2024 11:11:07 -0600 Subject: [PATCH 146/236] style removing whitespace from copyright notice. adding dash to changelog header --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- com.unity.netcode.gameobjects/LICENSE.md | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3b068b5c21..c3137ad6be 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [2.2.0] 2024-12-12 +## [2.2.0] - 2024-12-12 ### Added diff --git a/com.unity.netcode.gameobjects/LICENSE.md b/com.unity.netcode.gameobjects/LICENSE.md index 6e26290e2d..ee8cecf4bf 100644 --- a/com.unity.netcode.gameobjects/LICENSE.md +++ b/com.unity.netcode.gameobjects/LICENSE.md @@ -1,7 +1,5 @@ Unity Companion License (UCL License) -com.unity.netcode.gameobjects copyright © 2024 Unity Technologies +com.unity.netcode.gameobjects copyright © 2024 Unity Technologies Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license). Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. - - From 1914d3c62f6b0d703d852397d334cb992e60e477 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 12 Dec 2024 14:03:04 -0600 Subject: [PATCH 147/236] style Removing whitespaces for PVP-124-2 issues. --- com.unity.netcode.gameobjects/CHANGELOG.md | 8 +- .../Editor/NetworkManagerEditor.cs | 2 +- .../Editor/NetworkObjectEditor.cs | 6 +- .../Runtime/Components/NetworkAnimator.cs | 33 ++--- .../Components/NetworkRigidBodyBase.cs | 44 +++---- .../Runtime/Components/NetworkTransform.cs | 116 +++++++++--------- .../RigidbodyContactEventManager.cs | 10 +- .../Connection/NetworkConnectionManager.cs | 2 +- .../Runtime/Core/NetworkManager.cs | 2 +- .../Runtime/Core/NetworkObject.cs | 2 +- .../Runtime/Core/NetworkObjectRefreshTool.cs | 2 +- .../Runtime/Messaging/ILPPMessageProvider.cs | 2 +- .../Messages/ChangeOwnershipMessage.cs | 10 +- .../Messaging/Messages/CreateObjectMessage.cs | 2 +- .../Messages/NetworkTransformMessage.cs | 2 +- .../Runtime/Serialization/BitWriter.cs | 2 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 16 +-- .../Bootstrap/Scripts/Bootstrap.asmdef | 4 +- .../NetworkTransformOwnershipTests.cs | 2 +- .../NetworkTransform/NetworkTransformTests.cs | 2 +- .../NetworkVariableCollectionsTests.cs | 12 +- .../Prefabs/NetworkPrefabOverrideTests.cs | 4 +- 22 files changed, 146 insertions(+), 139 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c3137ad6be..accae93b14 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -215,8 +215,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [2.0.0-exp.2] - 2024-04-02 ### Added -- Added updates to all internal messages to account for a distributed authority network session connection. (#2863) -- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) +- Added updates to all internal messages to account for a distributed authority network session connection. (#2863) +- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) - For a customized `NetworkRigidbodyBase` class: - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes. - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned. @@ -352,6 +352,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where you could not have multiple source network prefab overrides targeting the same network prefab as their override. (#2710) ### Changed + - Changed the server or host shutdown so it will now perform a "soft shutdown" when `NetworkManager.Shutdown` is invoked. This will send a disconnect notification to all connected clients and the server-host will wait for all connected clients to disconnect or timeout after a 5 second period before completing the shutdown process. (#2789) - Changed `OnClientDisconnectedCallback` will now return the assigned client identifier on the local client side if the client was approved and assigned one prior to being disconnected. (#2789) - Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777) @@ -364,6 +365,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Changed in-scene placed `NetworkObject`s now set their `IsSceneObject` value when generating their `GlobalObjectIdHash` value. (#2710) - Changed the default `NetworkConfig.SpawnTimeout` value from 1.0s to 10.0s. (#2710) + ## [1.7.1] - 2023-11-15 ### Added @@ -413,7 +415,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added a protected virtual method `NetworkTransform.OnInitialize(ref NetworkTransformState replicatedState)` that just returns the replicated state reference. - + ### Fixed - Fixed issue where invoking `NetworkManager.Shutdown` within `NetworkManager.OnClientStopped` or `NetworkManager.OnServerStopped` would force `NetworkManager.ShutdownInProgress` to remain true after completing the shutdown process. (#2661) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 0684d74849..8ba4128b07 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -237,7 +237,7 @@ private void DisplayNetworkManagerProperties() if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) { EditorGUILayout.PropertyField(m_AutoSpawnPlayerPrefabClientSide, new GUIContent("Auto Spawn Player Prefab")); - } + } #endif EditorGUILayout.PropertyField(m_PlayerPrefabProperty, new GUIContent("Default Player Prefab")); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index bd18473b27..6adb7b8223 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -193,9 +193,9 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten // The below can cause visual anomalies and/or throws an exception within the EditorGUI itself (index out of bounds of the array). and has // The visual anomaly is when you select one field it is set in the drop down but then the flags selection in the popup menu selects more items - // even though if you exit the popup menu the flag setting is correct. - //var ownership = (NetworkObject.OwnershipStatus)EditorGUI.EnumFlagsField(position, label, (NetworkObject.OwnershipStatus)property.enumValueFlag); - //property.enumValueFlag = (int)ownership; + // even though if you exit the popup menu the flag setting is correct. + // var ownership = (NetworkObject.OwnershipStatus)EditorGUI.EnumFlagsField(position, label, (NetworkObject.OwnershipStatus)property.enumValueFlag); + // property.enumValueFlag = (int)ownership; EditorGUI.EndDisabledGroup(); EditorGUI.EndProperty(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 7f2c4ae23a..f6f40ea2b7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -416,8 +416,8 @@ internal struct AnimationMessage : INetworkSerializable internal bool HasBeenProcessed; // This is preallocated/populated in OnNetworkSpawn for all instances in the event ownership or - // authority changes. When serializing, IsDirtyCount determines how many AnimationState entries - // should be serialized from the list. When deserializing the list is created and populated with + // authority changes. When serializing, IsDirtyCount determines how many AnimationState entries + // should be serialized from the list. When deserializing the list is created and populated with // only the number of AnimationStates received which is dictated by the deserialized IsDirtyCount. internal List AnimationStates; @@ -493,7 +493,7 @@ internal bool IsServerAuthoritative() } /// - /// Override this method and return false to switch to owner authoritative mode + /// Override this method and return false to switch to owner authoritative mode. /// /// /// When using a distributed authority network topology, this will default to @@ -731,7 +731,7 @@ public override void OnNetworkDespawn() } /// - /// Wries all parameter and state information needed to initially synchronize a client + /// Writes all parameter and state information needed to initially synchronize a client /// private void WriteSynchronizationData(ref BufferSerializer serializer) where T : IReaderWriter { @@ -806,8 +806,10 @@ private void WriteSynchronizationData(ref BufferSerializer serializer) whe } } - animationState.Transition = isInTransition; // The only time this could be set to true - animationState.StateHash = stateHash; // When a transition, this is the originating/starting state + // The only time this could be set to true + animationState.Transition = isInTransition; + // When a transition, this is the originating/starting state + animationState.StateHash = stateHash; animationState.NormalizedTime = normalizedTime; animationState.Layer = layer; animationState.Weight = m_LayerWeights[layer]; @@ -881,7 +883,8 @@ private void CheckForStateChange(int layer) { m_TransitionHash[layer] = nt.fullPathHash; m_AnimationHash[layer] = 0; - animState.DestinationStateHash = nt.fullPathHash; // Next state is the destination state for cross fade + // Next state is the destination state for cross fade + animState.DestinationStateHash = nt.fullPathHash; animState.CrossFade = true; animState.Transition = true; animState.Duration = tt.duration; @@ -899,7 +902,8 @@ private void CheckForStateChange(int layer) // first time in this transition for this layer m_TransitionHash[layer] = tt.fullPathHash; m_AnimationHash[layer] = 0; - animState.StateHash = tt.fullPathHash; // Transitioning from state + // Transitioning from state + animState.StateHash = tt.fullPathHash; animState.CrossFade = false; animState.Transition = true; animState.NormalizedTime = tt.normalizedTime; @@ -1115,7 +1119,7 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) { writer.Seek(0); writer.Truncate(); - // Write how many parameter entries we are going to write + // Write out how many parameter entries to read BytePacker.WriteValuePacked(writer, (uint)m_ParametersToUpdate.Count); foreach (var parameterIndex in m_ParametersToUpdate) { @@ -1264,7 +1268,7 @@ internal void UpdateAnimationState(AnimationState animationState) NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) sub-table does not contain destination state ({animationState.DestinationStateHash})!"); } } - // For reference, it is valid to have no transition information + // For reference, it is valid to have no transition information //else if (NetworkManager.LogLevel == LogLevel.Developer) //{ // NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!"); @@ -1471,7 +1475,7 @@ private void InternalSetTrigger(int hash, bool isSet = true) /// /// Distributed Authority: Internally-called RPC client receiving function to update a trigger when the server wants to forward - /// a trigger for a client to play / reset + /// a trigger to a client /// /// the payload containing the trigger data to apply [Rpc(SendTo.NotAuthority)] @@ -1482,7 +1486,7 @@ internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage /// /// Client Server: Internally-called RPC client receiving function to update a trigger when the server wants to forward - /// a trigger for a client to play / reset + /// a trigger to a client /// /// the payload containing the trigger data to apply /// unused @@ -1548,7 +1552,7 @@ public void SetTrigger(int hash, bool setTrigger = true) } /// - /// Resets the trigger for the associated animation. See SetTrigger for more on how triggers are special + /// Resets the trigger for the associated animation. See SetTrigger for more on how triggers are special /// /// The string name of the trigger to reset public void ResetTrigger(string triggerName) @@ -1564,4 +1568,5 @@ public void ResetTrigger(int hash) } } } -#endif // COM_UNITY_MODULES_ANIMATION +// COM_UNITY_MODULES_ANIMATION +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 7e8808171a..18b393bec0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -180,7 +180,7 @@ internal Vector3 GetAdjustedRotationThreshold() /// /// /// For , only the x and y components of the are applied. - /// + /// public void SetLinearVelocity(Vector3 linearVelocity) { if (m_IsRigidbody2D) @@ -546,7 +546,7 @@ public void SetIsKinematic(bool isKinematic) { if (IsKinematic()) { - // If not already set to interpolate then set the Rigidbody to interpolate + // If not already set to interpolate then set the Rigidbody to interpolate if (m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate) { // Sleep until the next fixed update when switching from extrapolation to interpolation @@ -849,36 +849,30 @@ public void DetachFromFixedJoint() } if (UseRigidBodyForMotion) { - if (m_IsRigidbody2D) + if (m_IsRigidbody2D && FixedJoint2D != null) { - if (FixedJoint2D != null) + if (!m_FixedJoint2DUsingGravity) { - if (!m_FixedJoint2DUsingGravity) - { - FixedJoint2D.connectedBody.gravityScale = m_OriginalGravityScale; - } - FixedJoint2D.connectedBody = null; - Destroy(FixedJoint2D); - FixedJoint2D = null; - ResetInterpolation(); - RemoveFromParentBody(); + FixedJoint2D.connectedBody.gravityScale = m_OriginalGravityScale; } + FixedJoint2D.connectedBody = null; + Destroy(FixedJoint2D); + FixedJoint2D = null; + ResetInterpolation(); + RemoveFromParentBody(); } - else + else if (FixedJoint != null) { - if (FixedJoint != null) - { - FixedJoint.connectedBody = null; - m_InternalRigidbody.useGravity = m_OriginalGravitySetting; - Destroy(FixedJoint); - FixedJoint = null; - ResetInterpolation(); - RemoveFromParentBody(); - } + FixedJoint.connectedBody = null; + m_InternalRigidbody.useGravity = m_OriginalGravitySetting; + Destroy(FixedJoint); + FixedJoint = null; + ResetInterpolation(); + RemoveFromParentBody(); } } } } } -#endif // COM_UNITY_MODULES_PHYSICS - +// COM_UNITY_MODULES_PHYSICS +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index a103b0fc4b..3d2be47c63 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -32,7 +32,8 @@ public class NetworkTransform : NetworkBehaviour /// public struct NetworkTransformState : INetworkSerializable { - private const int k_InLocalSpaceBit = 0x00000001; // Persists between state updates (authority dictates if this is set) + // Persists between state updates (authority dictates if this is set) + private const int k_InLocalSpaceBit = 0x00000001; private const int k_PositionXBit = 0x00000002; private const int k_PositionYBit = 0x00000004; private const int k_PositionZBit = 0x00000008; @@ -43,18 +44,25 @@ public struct NetworkTransformState : INetworkSerializable private const int k_ScaleYBit = 0x00000100; private const int k_ScaleZBit = 0x00000200; private const int k_TeleportingBit = 0x00000400; - private const int k_Interpolate = 0x00000800; // Persists between state updates (authority dictates if this is set) - private const int k_QuaternionSync = 0x00001000; // Persists between state updates (authority dictates if this is set) - private const int k_QuaternionCompress = 0x00002000; // Persists between state updates (authority dictates if this is set) - private const int k_UseHalfFloats = 0x00004000; // Persists between state updates (authority dictates if this is set) + // Persists between state updates (authority dictates if this is set) + private const int k_Interpolate = 0x00000800; + // Persists between state updates (authority dictates if this is set) + private const int k_QuaternionSync = 0x00001000; + // Persists between state updates (authority dictates if this is set) + private const int k_QuaternionCompress = 0x00002000; + // Persists between state updates (authority dictates if this is set) + private const int k_UseHalfFloats = 0x00004000; private const int k_Synchronization = 0x00008000; - private const int k_PositionSlerp = 0x00010000; // Persists between state updates (authority dictates if this is set) - private const int k_IsParented = 0x00020000; // When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order + // Persists between state updates (authority dictates if this is set) + private const int k_PositionSlerp = 0x00010000; + // When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order + private const int k_IsParented = 0x00020000; private const int k_SynchBaseHalfFloat = 0x00040000; private const int k_ReliableSequenced = 0x00080000; private const int k_UseUnreliableDeltas = 0x00100000; private const int k_UnreliableFrameSync = 0x00200000; - private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier + // (Internal Debugging) When set each state update will contain a state identifier + private const int k_TrackStateId = 0x10000000; // Stores persistent and state relative flags private uint m_Bitset; @@ -409,8 +417,8 @@ internal set } /// - /// Returns whether this state update was a frame synchronization when - /// UseUnreliableDeltas is enabled. When set, the entire transform will + /// Returns whether this state update was a frame synchronization when + /// UseUnreliableDeltas is enabled. When set, the entire transform will /// be or has been synchronized. /// public bool IsUnreliableFrameSync() @@ -929,8 +937,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endregion #region PROPERTIES AND GENERAL METHODS - - public enum AuthorityModes { Server, @@ -1370,7 +1376,8 @@ internal NetworkTransformState LocalAuthoritativeNetworkState private BufferedLinearInterpolatorVector3 m_PositionInterpolator; private BufferedLinearInterpolatorVector3 m_ScaleInterpolator; - private BufferedLinearInterpolatorQuaternion m_RotationInterpolator; // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + private BufferedLinearInterpolatorQuaternion m_RotationInterpolator; // The previous network state private NetworkTransformState m_OldState = new NetworkTransformState(); @@ -1643,11 +1650,11 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz Debug.LogException(ex); } - // The below is part of assuring we only send a frame synch, when sending unreliable deltas, if + // The below is part of assuring we only send a frame synch, when sending unreliable deltas, if // we have already sent at least one unreliable delta state update. At this point in the callstack, // a delta state update has just been sent in the above UpdateTransformState() call and as long as // we didn't send a frame synch and we are not synchronizing then we know at least one unreliable - // delta has been sent. Under this scenario, we should start checking for this instance's alloted + // delta has been sent. Under this scenario, we should start checking for this instance's alloted // frame synch "tick slot". Once we send a frame synch, if no other deltas occur after that // (i.e. the object is at rest) then we will stop sending frame synch's until the object begins // moving, rotating, or scaling again. @@ -1964,7 +1971,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.NetworkDeltaPosition = m_HalfPositionState; - // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position + // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position if ((m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick || isAxisSync) && !networkState.IsTeleportingNextFrame) { networkState.SynchronizeBaseHalfFloat = true; @@ -3403,7 +3410,7 @@ internal void ChildRegistration(NetworkObject child, bool isAdding) /// - Local space to local space ( parent to parent) /// Will all smoothly transition while interpolation is enabled. /// (Does not work if using a or for motion) - /// + /// /// When a parent changes, non-authoritative instances should:
/// - Apply the resultant position, rotation, and scale from the parenting action.
/// - Clear interpolators (even if not enabled on this frame)
@@ -3575,7 +3582,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s var transformToCommit = transform; - // Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will + // Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will // update the axial deltas to whatever changes are applied). As such, we need to preserve the dirty and explicit // state flags. var stateWasDirty = m_LocalAuthoritativeNetworkState.IsDirty; @@ -3658,7 +3665,7 @@ private void UpdateInterpolation() var serverTime = m_CachedNetworkManager.ServerTime; var cachedServerTime = serverTime.Time; - //var offset = (float)serverTime.TickOffset; + // var offset = (float)serverTime.TickOffset; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else @@ -3669,7 +3676,7 @@ private void UpdateInterpolation() // is to make their cachedRenderTime run 2 ticks behind. // TODO: This could most likely just always be 2 - //var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; + // var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; var ticksAgo = 2; var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; @@ -3746,7 +3753,7 @@ public virtual void OnFixedUpdate() /// /// Determines whether the is or based on the property. - /// You can override this method to control this logic. + /// You can override this method to control this logic. /// /// or protected virtual bool OnIsServerAuthoritative() @@ -3772,7 +3779,6 @@ public bool IsServerAuthoritative() return OnIsServerAuthoritative(); } } - #endregion #region MESSAGE HANDLING @@ -3964,7 +3970,7 @@ internal float TicksAgoInSeconds() { return 2 * m_TickFrequency; // TODO: We need an RTT that updates regularly and not just when the client sends packets - //return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; + // return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; } /// @@ -3974,25 +3980,25 @@ internal float TicksAgoInSeconds() private void TickUpdate() { // TODO: We need an RTT that updates regularly and not just when the client sends packets - //if (m_UnityTransport != null) - //{ - // // Determine the desired ticks ago by the RTT (this really should be the combination of the - // // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor - // // network quality so latent interpolation is going to be expected). - // var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); - // m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); - // var tickAgoSum = 0.0f; - // foreach (var tickAgo in m_TicksAgoSamples) - // { - // tickAgoSum += tickAgo; - // } - // m_PreviousTicksAgo = TicksAgo; - // TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); - // m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; - // // Get the partial tick value for when this is all calculated to provide an offset for determining - // // the relative starting interpolation point for the next update - // Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); - //} + // if (m_UnityTransport != null) + // { + // // Determine the desired ticks ago by the RTT (this really should be the combination of the + // // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor + // // network quality so latent interpolation is going to be expected). + // var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); + // m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); + // var tickAgoSum = 0.0f; + // foreach (var tickAgo in m_TicksAgoSamples) + // { + // tickAgoSum += tickAgo; + // } + // m_PreviousTicksAgo = TicksAgo; + // TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); + // m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; + // // Get the partial tick value for when this is all calculated to provide an offset for determining + // // the relative starting interpolation point for the next update + // Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); + // } // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before if (m_NetworkManager.ServerTime.Tick <= m_LastTick) @@ -4012,13 +4018,13 @@ private void TickUpdate() private UnityTransport m_UnityTransport; private float m_TickFrequency; - //private float m_OffsetTickFrequency; - //private ulong m_TickInMS; - //private int m_TickSampleIndex; + // private float m_OffsetTickFrequency; + // private ulong m_TickInMS; + // private int m_TickSampleIndex; private int m_TickRate; public float TicksAgo { get; private set; } - //public float Offset { get; private set; } - //private float m_PreviousTicksAgo; + // public float Offset { get; private set; } + // private float m_PreviousTicksAgo; private List m_TicksAgoSamples = new List(); @@ -4032,16 +4038,16 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) //// For the offset, it uses the fractional remainder of the tick to determine the offset. //// In order to keep within tick boundaries, we increment the tick rate by 1 to assure it //// will always be < the tick frequency. - //m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); - //m_TickInMS = (ulong)(1000 * m_TickFrequency); - //m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; + // m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); + // m_TickInMS = (ulong)(1000 * m_TickFrequency); + // m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; //// Fill the sample with a starting value of 1 - //for (int i = 0; i < m_TickRate; i++) - //{ - // m_TicksAgoSamples.Add(1f); - //} + // for (int i = 0; i < m_TickRate; i++) + // { + // m_TicksAgoSamples.Add(1f); + // } TicksAgo = 2f; - //m_PreviousTicksAgo = 1f; + // m_PreviousTicksAgo = 1f; if (networkManager.IsServer) { networkManager.OnServerStopped += OnNetworkManagerStopped; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index d0808d2886..02f9c98e7e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -27,7 +27,7 @@ public struct ContactEventHandlerInfo } /// - /// Default implementation required to register a with a instance. + /// Default implementation required to register a with a instance. /// /// /// Recommended to implement this method on a component @@ -52,7 +52,7 @@ public interface IContactEventHandler } /// - /// This is an extended version of and can be used to register a with a instance.
+ /// This is an extended version of and can be used to register a with a instance.
/// This provides additional information to the for each set of contact events it is processing. ///
public interface IContactEventHandlerWithInfo : IContactEventHandler @@ -66,9 +66,9 @@ public interface IContactEventHandlerWithInfo : IContactEventHandler /// /// Add this component to an in-scene placed GameObject to provide faster collision event processing between instances and optionally static colliders. - ///
- ///
- ///
+ ///
+ ///
+ ///
///
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")] public class RigidbodyContactEventManager : MonoBehaviour diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 09119e8076..e297375ec1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode { /// - /// The connection event type set within to signify the type of connection event notification received. + /// The connection event type set within to signify the type of connection event notification received. /// /// /// is returned as a parameter of the event notification. diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 6a7d71f159..75db75b9f2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -416,7 +416,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed. MetricsManager.UpdateMetrics(); - // Handle sending any pending transport messages + // Handle sending any pending transport messages NetworkConfig.NetworkTransport.PostLateUpdate(); // TODO: Determine a better way to handle this diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 5d94f4fd3c..2ff4e27eb3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -182,7 +182,7 @@ private static void PrefabStageOpened(PrefabStage prefabStage) /// /// InContext: Typically means a are in prefab edit mode for an in-scene placed network prefab instance. /// (currently no such thing as a network prefab with nested network prefab instances) - /// + /// /// InIsolation: Typically means we are in prefb edit mode for a prefab asset. /// /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs index 63d48e914d..5309377475 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode { /// - /// This is a helper tool to update all in-scene placed instances of a prefab that + /// This is a helper tool to update all in-scene placed instances of a prefab that /// originally did not have a NetworkObject component but one was added to the prefab /// later. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index b7f90f658f..4e697acc0d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -16,7 +16,7 @@ internal struct ILPPMessageProvider : INetworkMessageProvider /// /// Enum representing the different types of messages that can be sent over the network. /// The values cannot be changed, as they are used to serialize and deserialize messages. - /// Adding new messages should be done by adding new values to the end of the enum + /// Adding new messages should be done by adding new values to the end of the enum /// using the next free value. /// /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index cb4f114b91..998e84d640 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -17,7 +17,7 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem /// /// When requesting, RequestClientId is the requestor. /// When approving, RequestClientId is the owner that approved. - /// When responding (only for denied), RequestClientId is the requestor + /// When responding (only for denied), RequestClientId is the requestor /// internal ulong RequestClientId; internal int ClientIdCount; @@ -272,7 +272,7 @@ public void Handle(ref NetworkContext context) networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, clientId); } } - // If the NetworkObject is not visible to the DAHost client, then exit early + // If the NetworkObject is not visible to the DAHost client, then exit early if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { return; @@ -294,7 +294,7 @@ public void Handle(ref NetworkContext context) } /// - /// Handle the + /// Handle the extended distributed authority ownership updates /// /// private void HandleExtendedOwnershipUpdate(ref NetworkContext context) @@ -351,10 +351,10 @@ private void HandleOwnershipChange(ref NetworkContext context) networkObject.InvokeBehaviourOnLostOwnership(); } - // If in distributed authority mode + // If in distributed authority mode if (networkManager.DistributedAuthorityMode) { - // Always update the network properties in distributed authority mode + // Always update the network properties in distributed authority mode for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) { networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 6c0656b90a..02f4263e9d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -257,7 +257,7 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende { // DA - NGO CMB SERVICE NOTES: // (*** See above notes fist ***) - // If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively + // If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively // the same frame), then we need to check the observers list to make sure all players are visible upon first spawning. At a later date, // for area of interest we will need to have some form of follow up "observer update" message to cull out players not within each // player's AOI. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs index 56f25f22c2..cf4013f469 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs @@ -128,7 +128,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } else if (networkManager.DAHost) { - // Specific to distributed authority mode, the only sender of state updates will be the owner + // Specific to distributed authority mode, the only sender of state updates will be the owner ownerClientId = context.SenderId; } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs index 9eacd5601f..0e3ccfb7e3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs @@ -62,7 +62,7 @@ public void Dispose() /// When you know you will be writing multiple fields back-to-back and you know the total size, /// you can call TryBeginWriteBits() once on the total size, and then follow it with calls to /// WriteBit() or WriteBits(). - /// + /// /// Bitwise write operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginWriteBits(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 9ce6595c5f..85ff9a89e2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -771,7 +771,7 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ } /// - /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the + /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the /// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned. /// internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false) @@ -803,8 +803,8 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Prefab: { - // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the - // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically + // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the + // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically // but might want to use the same source network prefab as an in-scene placed NetworkObject. // (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically // spawning them so the original prefab placed is preserved and this is not needed) @@ -998,7 +998,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO /// - NetworkObject when spawning a newly instantiated NetworkObject for the first time. /// - NetworkSceneManager after a server/session-owner has loaded a scene to locally spawn the newly instantiated in-scene placed NetworkObjects. /// - NetworkSpawnManager when spawning any already loaded in-scene placed NetworkObjects (client-server or session owner). - /// + /// /// Client-Server: /// Server is the only instance that invokes this method. /// @@ -1376,7 +1376,7 @@ internal void DespawnAndDestroyNetworkObjects() } } - // If spawned, then despawn and potentially destroy. + // If spawned, then despawn and potentially destroy. if (networkObjects[i].IsSpawned) { OnDespawnObject(networkObjects[i], shouldDestroy); @@ -1805,7 +1805,7 @@ internal void DistributeNetworkObjects(ulong clientId) } - // DA-NGO CMB SERVICE NOTES: + // DA-NGO CMB SERVICE NOTES: // The most basic object distribution should be broken up into a table of spawned object types // where each type contains a list of each client's owned objects of that type that can be // distributed. @@ -1824,7 +1824,7 @@ internal void DistributeNetworkObjects(ulong clientId) var clientCount = NetworkManager.ConnectedClientsIds.Count; - // Cycle through each prefab type + // Cycle through each prefab type foreach (var objectTypeEntry in distributedNetworkObjects) { // Calculate the number of objects that should be distributed amongst the clients @@ -1894,7 +1894,7 @@ internal void DistributeNetworkObjects(ulong clientId) objectTypeCount.Clear(); GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); builder.AppendLine($"Client Relative Distributed Object Count: (distribution follows)"); - // Cycle through each prefab type + // Cycle through each prefab type foreach (var objectTypeEntry in distributedNetworkObjects) { builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}][Total Count: {objectTypeCount[objectTypeEntry.Key]}]"); diff --git a/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef index 07a8ac0096..54cb675609 100644 --- a/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef +++ b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef @@ -1,7 +1,7 @@ { - "name": "Bootstrap", + "name": "Bootstrap", "rootNamespace": "Unity.Netcode.Samples", "references": [ "Unity.Netcode.Runtime" ] -} +} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index ca402c6095..229a067758 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -869,7 +869,7 @@ public IEnumerator NestedNetworkTransformSpawnPositionTest() foreach (var networkManager in m_NetworkManagers) { - // Randomize the position + // Randomize the position RandomizeObjectTransformPositions(m_SpawnObject); // Create an instance owned by the specified networkmanager diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 95ca7e0f20..7e12934a67 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -123,7 +123,7 @@ private void UpdateTransformWorld(Components.NetworkTransform networkTransformTe } /// - /// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies + /// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies /// [Test] public void SwitchTransformSpaceWhenParentedTest([Values(0.5f, 1.0f, 5.0f)] float scale) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs index 0c8a2ffdf9..3276b3d3df 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs @@ -20,7 +20,7 @@ namespace Unity.Netcode.RuntimeTests /// - HashSet /// This also does some testing on nested collections, but does /// not test every possible combination. - ///
+ ///
[TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkVariableCollectionsTests : NetcodeIntegrationTest @@ -218,7 +218,7 @@ public IEnumerator TestListBuiltInTypeCollections() yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// - // Server Full Set + // Server Full Set compIntServer.FullSet(GetRandomIntList(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); @@ -285,7 +285,7 @@ public IEnumerator TestListBuiltInTypeCollections() // Only test restore on non-host clients (otherwise a host is both server and client/owner) if (!client.IsServer) { - // No Write Client Remove List item with CheckDirtyState restore + // No Write Client Remove List item with CheckDirtyState restore compListInt.Remove(compListInt.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to restore on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); @@ -474,7 +474,7 @@ public IEnumerator TestListSerializableObjectCollections() yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to restore on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); - // No Write Client Remove Serializable item with owner state update restore + // No Write Client Remove Serializable item with owner state update restore compObject.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); } compObjectServer.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); @@ -838,7 +838,7 @@ public IEnumerator TestDictionaryCollections() compDictionaryServer.ListCollectionOwner.IsDirty(); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Server add to owner write collection property failed to restore on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); - // Server-side add the same key and SerializableObject to owner write permission (would throw key exists exception too if previous failed) + // Server-side add the same key and SerializableObject to owner write permission (would throw key exists exception too if previous failed) compDictionaryServer.ListCollectionOwner.Value.Add(newEntry.Item1, newEntry.Item2); // Server-side add a completely new key and SerializableObject to to owner write permission property compDictionaryServer.ListCollectionOwner.Value.Add(GetNextKey(), SerializableObject.GetRandomObject()); @@ -864,7 +864,7 @@ public IEnumerator TestDictionaryCollections() compDictionary.ListCollectionServer.IsDirty(); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Client-{client.LocalClientId} add to server write collection property failed to restore on {className} {compDictionary.name}! {compDictionary.GetLog()}"); - // Client-side add the same key and SerializableObject to server write permission property (would throw key exists exception too if previous failed) + // Client-side add the same key and SerializableObject to server write permission property (would throw key exists exception too if previous failed) compDictionary.ListCollectionServer.Value.Add(newEntry.Item1, newEntry.Item2); // Client-side add a completely new key and SerializableObject to to server write permission property compDictionary.ListCollectionServer.Value.Add(GetNextKey(), SerializableObject.GetRandomObject()); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs index b7d86e3356..84d9c56fec 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs @@ -109,7 +109,7 @@ protected override void OnServerAndClientsCreated() playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab; playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab; - // Add the NetworkPrefab with override + // Add the NetworkPrefab with override m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride); // Add the client player prefab that will be used on clients (and the host) m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab); @@ -121,7 +121,7 @@ protected override void OnServerAndClientsCreated() playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab; playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab; - // Add the NetworkPrefab with override + // Add the NetworkPrefab with override networkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride); // Add the client player prefab that will be used on clients (and the host) networkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab); From 658ef01f0f96ed33bb7e17a3e9b4c202a6aba7c2 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 12 Dec 2024 14:53:54 -0600 Subject: [PATCH 148/236] style missed one whitespace... --- .../Runtime/Messaging/RpcTargets/ServerRpcTarget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 0db593f23b..87bfbfb1c4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -43,7 +43,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None); message.ReadBuffer = tempBuffer; message.Handle(ref context); - // If enabled, then add the RPC metrics for this + // If enabled, then add the RPC metrics for this #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE int length = tempBuffer.Length; if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) From 2b3893c99ead74e482e5335a0b66dedea83004c4 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 12 Dec 2024 14:55:05 -0600 Subject: [PATCH 149/236] fix: clamp spawntimeout to recommended range (#3174) * update Clamping spawntimeout * update improving parenting failed message when either the child or parent NetworkObject is not spawnd. * update Update the local SceneEventData.SceneEventType on the authority side for SceneLoadComplete. * style removing whitespaces --- .../Runtime/Configuration/NetworkConfig.cs | 25 +++++++++++++++++++ .../Runtime/Core/NetworkManager.cs | 3 +++ .../Runtime/Core/NetworkObject.cs | 8 +++--- .../SceneManagement/NetworkSceneManager.cs | 4 ++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 79035a895f..a9668084bd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -13,6 +13,14 @@ namespace Unity.Netcode [Serializable] public class NetworkConfig { + // Clamp spawn time outs to prevent dropping messages during scene events + // Note: The legacy versions of NGO defaulted to 1s which was too low. As + // well, the SpawnTimeOut is now being clamped to within this recommended + // range both via UI and when NetworkManager is validated. + internal const float MinSpawnTimeout = 10.0f; + // Clamp spawn time outs to no more than 1 hour (really that is a bit high) + internal const float MaxSpawnTimeout = 3600.0f; + /// /// The protocol version. Different versions doesn't talk to each other. /// @@ -132,6 +140,8 @@ public class NetworkConfig /// The amount of time a message will be held (deferred) if the destination NetworkObject needed to process the message doesn't exist yet. If the NetworkObject is not spawned within this time period, all deferred messages for that NetworkObject will be dropped. ///
[Tooltip("The amount of time a message will be held (deferred) if the destination NetworkObject needed to process the message doesn't exist yet. If the NetworkObject is not spawned within this time period, all deferred messages for that NetworkObject will be dropped.")] + + [Range(MinSpawnTimeout, MaxSpawnTimeout)] public float SpawnTimeout = 10f; /// @@ -176,6 +186,21 @@ public class NetworkConfig [Tooltip("Enable (default) if you want to profile network messages with development builds and defaults to being disabled in release builds. When disabled, network messaging profiling will be disabled in development builds.")] public bool NetworkProfilingMetrics = true; + /// + /// Invoked by when it is validated. + /// + /// + /// Used to check for potential legacy values that have already been serialized and/or + /// runtime modifications to a property outside of the recommended range. + /// For each property checked below, provide a brief description of the reason. + /// + internal void OnValidate() + { + // Legacy NGO versions defaulted this value to 1 second that has since been determiend + // any range less than 10 seconds can lead to dropped messages during scene events. + SpawnTimeout = Mathf.Clamp(SpawnTimeout, MinSpawnTimeout, MaxSpawnTimeout); + } + /// /// Returns a base64 encoded version of the configuration /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 6a7d71f159..4f8c56e1f4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -955,6 +955,9 @@ internal void OnValidate() return; // May occur when the component is added } + // Do a validation pass on NetworkConfig properties + NetworkConfig.OnValidate(); + if (GetComponentInChildren() != null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 5d94f4fd3c..1606343cca 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2007,12 +2007,14 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent != null && (IsSpawned ^ parent.IsSpawned)) + if (parent != null && (IsSpawned ^ parent.IsSpawned) && NetworkManager != null && !NetworkManager.ShutdownInProgress) { - if (NetworkManager != null && !NetworkManager.ShutdownInProgress) + if (NetworkManager.LogLevel <= LogLevel.Developer) { - return false; + var nameOfNotSpawnedObject = IsSpawned ? $" the parent ({parent.name})" : $"the child ({name})"; + NetworkLog.LogWarning($"Parenting failed because {nameOfNotSpawnedObject} is not spawned!"); } + return false; } m_CachedWorldPositionStays = worldPositionStays; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index acffc411f8..8996dfcfd7 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1903,10 +1903,12 @@ private void OnSessionOwnerLoadedScene(uint sceneEventId, Scene scene) SendSceneEventData(sceneEventData.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != sessionOwner).ToArray()); m_IsSceneEventActive = false; + + sceneEventData.SceneEventType = SceneEventType.LoadComplete; //First, notify local server that the scene was loaded OnSceneEvent?.Invoke(new SceneEvent() { - SceneEventType = SceneEventType.LoadComplete, + SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, SceneName = SceneNameFromHash(sceneEventData.SceneHash), ClientId = NetworkManager.LocalClientId, From 2d1097583a80241c19e49b6d0025371e2ce54a73 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 13 Dec 2024 01:29:25 -0500 Subject: [PATCH 150/236] feat: Add a SessionOwner ObjectStatus and allow InScenePlaced network objects to be distributed (#3175) * Initial pass on SessionOwner ownership flag * feat: Add SessionOwner OwnershipStatus flag * update removing additional session owner accessor. * Add OwnershipPermissions tests * fix client connect * Revert "fix client connect" This reverts commit 3c3b35444200da308dd07e1dd2e7b591fa0842d5. * update object distribution for in-scene placed NetworkObjects needs to use the InScenePlacedSourceGlobalObjectIdHash when building an object distribution list. * Add changelog * Remove unnecessary change * Remove Settings.json * Reword CHANGELOG * fix DAHost should not distribute session owner permission NetworkObjects. When client is promoted to session owner, for now newly promoted client takes ownership of NetworkObjects that have the SessionOwner permission set. Only prevent non-session owners from taking ownership of a NetworkObject with the SessionOwner permission set. * test fix Avoid the RemoveOwnership client-server only method. * style Visual studio code cleanup likes to sort by alpha... fixing for our standards. * update Adding check for session owner trying to change ownership to a non-session owner client. * test Adding an additional validation that a non-session owner cannot change ownership and that a session owner cannot change ownership to a non-session owner when the NetworkObject in question has the SessionOwner permissions set. --------- Co-authored-by: NoelStephensUnity --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Editor/NetworkObjectEditor.cs | 6 +- .../Connection/NetworkConnectionManager.cs | 9 +- .../Runtime/Core/NetworkManager.cs | 8 +- .../Runtime/Core/NetworkObject.cs | 62 ++++++++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 83 +++++++------ .../OwnershipPermissionsTests.cs | 112 ++++++++++++++---- .../NetworkObjectOwnershipTests.cs | 13 +- 8 files changed, 207 insertions(+), 88 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2d16bbc30e..d253135563 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `NetworkObject.OwnershipStatus.SessionOwner` to allow Network Objects to be distributable and only owned by the Session Owner. This flag will override all other `OwnershipStatus` flags. (#3175) - Added `UnityTransport.GetEndpoint` method to provide a way to obtain `NetworkEndpoint` information of a connection via client identifier. (#3130) - Added `NetworkTransport.OnEarlyUpdate` and `NetworkTransport.OnPostLateUpdate` methods to provide more control over handling transport related events at the start and end of each frame. (#3113) @@ -30,6 +31,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- In-scene placed `NetworkObject`s have been made distributable when balancing object distribution after a connection event. (#3175) - Optimised `NetworkVariable` and `NetworkTransform` related packets when in Distributed Authority mode. - The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index bd18473b27..25dd8967f1 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -115,6 +115,10 @@ public override void OnInspectorGUI() EditorGUI.BeginChangeCheck(); serializedObject.UpdateIfRequiredOrScript(); DrawPropertiesExcluding(serializedObject, k_HiddenFields); + if (m_NetworkObject.IsOwnershipSessionOwner) + { + m_NetworkObject.Ownership = NetworkObject.OwnershipStatus.SessionOwner; + } serializedObject.ApplyModifiedProperties(); EditorGUI.EndChangeCheck(); @@ -193,7 +197,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten // The below can cause visual anomalies and/or throws an exception within the EditorGUI itself (index out of bounds of the array). and has // The visual anomaly is when you select one field it is set in the drop down but then the flags selection in the popup menu selects more items - // even though if you exit the popup menu the flag setting is correct. + // even though if you exit the popup menu the flag setting is correct. //var ownership = (NetworkObject.OwnershipStatus)EditorGUI.EnumFlagsField(position, label, (NetworkObject.OwnershipStatus)property.enumValueFlag); //property.enumValueFlag = (int)ownership; EditorGUI.EndDisabledGroup(); diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 09119e8076..fe6bc4816d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode { /// - /// The connection event type set within to signify the type of connection event notification received. + /// The connection event type set within to signify the type of connection event notification received. /// /// /// is returned as a parameter of the event notification. @@ -1212,7 +1212,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) { // Only NetworkObjects that have the OwnershipStatus.Distributable flag set and no parent // (ownership is transferred to all children) will have their ownership redistributed. - if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null) + if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null && !ownedObject.IsOwnershipSessionOwner) { if (ownedObject.IsOwnershipLocked) { @@ -1249,6 +1249,11 @@ internal void OnClientDisconnectFromServer(ulong clientId) childObject.SetOwnershipLock(false); } + // Ignore session owner marked objects + if (childObject.IsOwnershipSessionOwner) + { + continue; + } NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true); if (EnableDistributeLogging) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 4f8c56e1f4..9b3ef047a0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -225,11 +225,7 @@ internal void SetSessionOwner(ulong sessionOwner) foreach (var networkObjectEntry in SpawnManager.SpawnedObjects) { var networkObject = networkObjectEntry.Value; - if (networkObject.IsSceneObject == null || !networkObject.IsSceneObject.Value) - { - continue; - } - if (networkObject.OwnerClientId != LocalClientId) + if (networkObject.IsOwnershipSessionOwner && LocalClient.IsSessionOwner) { SpawnManager.ChangeOwnership(networkObject, LocalClientId, true); } @@ -416,7 +412,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed. MetricsManager.UpdateMetrics(); - // Handle sending any pending transport messages + // Handle sending any pending transport messages NetworkConfig.NetworkTransport.PostLateUpdate(); // TODO: Determine a better way to handle this diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 1606343cca..c449c9aadb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -182,7 +182,7 @@ private static void PrefabStageOpened(PrefabStage prefabStage) /// /// InContext: Typically means a are in prefab edit mode for an in-scene placed network prefab instance. /// (currently no such thing as a network prefab with nested network prefab instances) - /// + /// /// InIsolation: Typically means we are in prefb edit mode for a prefab asset. /// /// @@ -439,6 +439,13 @@ public void DeferDespawn(int tickOffset, bool destroy = true) /// public bool IsOwnershipDistributable => Ownership.HasFlag(OwnershipStatus.Distributable); + /// + /// When true, the can only be owned by the current Session Owner. + /// To set during runtime, use to ensure the session owner owns the object. + /// Once the session owner owns the object, then use . + /// + public bool IsOwnershipSessionOwner => Ownership.HasFlag(OwnershipStatus.SessionOwner); + /// /// Returns true if the is has ownership locked. /// When locked, the cannot be redistributed nor can it be transferred by another client. @@ -481,7 +488,8 @@ public void DeferDespawn(int tickOffset, bool destroy = true) /// : If nothing is set, then ownership is considered "static" and cannot be redistributed, requested, or transferred (i.e. a Player would have this). /// : When set, this instance will be automatically redistributed when a client joins (if not locked or no request is pending) or leaves. /// : When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked). - /// : When set, When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). + /// : When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). + /// : When set, only the current session owner may have ownership over this object. /// // Ranges from 1 to 8 bits [Flags] @@ -491,6 +499,7 @@ public enum OwnershipStatus Distributable = 1 << 0, Transferable = 1 << 1, RequestRequired = 1 << 2, + SessionOwner = 1 << 3, } /// @@ -549,7 +558,7 @@ public bool SetOwnershipLock(bool lockOwnership = true) } // If we don't have the Transferable flag set and it is not a player object, then it is the same as having a static lock on ownership - if (!IsOwnershipTransferable && !IsPlayerObject) + if (!(IsOwnershipTransferable || IsPlayerObject) || IsOwnershipSessionOwner) { NetworkLog.LogWarning($"Trying to add or remove ownership lock on [{name}] which does not have the {nameof(OwnershipStatus.Transferable)} flag set!"); return false; @@ -582,13 +591,15 @@ public bool SetOwnershipLock(bool lockOwnership = true) /// : The requires an ownership request via . /// : The is already processing an ownership request and ownership cannot be acquired at this time. /// : The does not have the flag set and ownership cannot be acquired. + /// : The has the flag set and ownership cannot be acquired. /// public enum OwnershipPermissionsFailureStatus { Locked, RequestRequired, RequestInProgress, - NotTransferrable + NotTransferrable, + SessionOwnerOnly } /// @@ -610,6 +621,7 @@ public enum OwnershipPermissionsFailureStatus /// : The flag is not set on this /// : The current owner has locked ownership which means requests are not available at this time. /// : There is already a known request in progress. You can scan for ownership changes and try upon + /// : This object is marked as SessionOwnerOnly and therefore cannot be requested /// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership. /// public enum OwnershipRequestStatus @@ -619,6 +631,7 @@ public enum OwnershipRequestStatus RequestRequiredNotSet, Locked, RequestInProgress, + SessionOwnerOnly, } /// @@ -631,6 +644,7 @@ public enum OwnershipRequestStatus /// : The flag is not set on this /// : The current owner has locked ownership which means requests are not available at this time. /// : There is already a known request in progress. You can scan for ownership changes and try upon + /// : This object can only belong the the session owner and so cannot be requested /// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership. /// /// @@ -660,6 +674,12 @@ public OwnershipRequestStatus RequestOwnership() return OwnershipRequestStatus.RequestInProgress; } + // Exit early if it has the SessionOwner flag + if (IsOwnershipSessionOwner) + { + return OwnershipRequestStatus.SessionOwnerOnly; + } + // Otherwise, send the request ownership message var changeOwnership = new ChangeOwnershipMessage { @@ -716,7 +736,7 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) { response = OwnershipRequestResponseStatus.RequestInProgress; } - else if (!IsOwnershipRequestRequired && !IsOwnershipTransferable) + else if (!(IsOwnershipRequestRequired || IsOwnershipTransferable) || IsOwnershipSessionOwner) { response = OwnershipRequestResponseStatus.CannotRequest; } @@ -836,6 +856,12 @@ public enum OwnershipLockActions /// public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, OwnershipLockActions lockAction = OwnershipLockActions.None) { + if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManager.LocalClient.IsSessionOwner) + { + NetworkLog.LogWarning("Only the session owner is allowed to set the ownership status to session owner only."); + return false; + } + // If it already has the flag do nothing if (!clearAndSet && Ownership.HasFlag(status)) { @@ -847,13 +873,25 @@ public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, Ownership = OwnershipStatus.None; } - // Faster to just OR a None status than to check - // if it is !None before "OR'ing". - Ownership |= status; - - if (lockAction != OwnershipLockActions.None) + if (status.HasFlag(OwnershipStatus.SessionOwner)) + { + Ownership = OwnershipStatus.SessionOwner; + } + else if (Ownership.HasFlag(OwnershipStatus.SessionOwner)) + { + NetworkLog.LogWarning("No other ownership statuses may be set while SessionOwner is set."); + return false; + } + else { - SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock); + // Faster to just OR a None status than to check + // if it is !None before "OR'ing". + Ownership |= status; + + if (lockAction != OwnershipLockActions.None) + { + SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock); + } } SendOwnershipStatusUpdate(); @@ -1629,7 +1667,7 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla // DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set if (NetworkManager.LogLevel == LogLevel.Developer) { - NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will automatically set DistributeOwnership."); + NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will set ownership to SessionOwner."); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 9ce6595c5f..ed0b0348b7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -422,27 +422,8 @@ internal void RemoveOwnership(NetworkObject networkObject) { if (NetworkManager.DistributedAuthorityMode && !NetworkManager.ShutdownInProgress) { - if (networkObject.IsOwnershipDistributable || networkObject.IsOwnershipTransferable) - { - if (networkObject.IsOwner || NetworkManager.DAHost) - { - NetworkLog.LogWarning("DANGO-TODO: Determine if removing ownership should make the CMB Service redistribute ownership or if this just isn't a valid thing in DAMode."); - return; - } - else - { - NetworkLog.LogError($"Only the owner is allowed to remove ownership in distributed authority mode!"); - return; - } - } - else - { - if (!NetworkManager.DAHost) - { - Debug.LogError($"Only {nameof(NetworkObject)}s with {nameof(NetworkObject.IsOwnershipDistributable)} or {nameof(NetworkObject.IsOwnershipTransferable)} set can perform ownership changes!"); - } - return; - } + Debug.LogError($"Removing ownership is invalid in Distributed Authority Mode. Use {nameof(ChangeOwnership)} instead."); + return; } ChangeOwnership(networkObject, NetworkManager.ServerClientId, true); } @@ -474,6 +455,18 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool if (NetworkManager.DistributedAuthorityMode) { + // Ensure only the session owner can change ownership (i.e. acquire) and that the session owner is not trying to assign a non-session owner client + // ownership of a NetworkObject with SessionOwner permissions. + if (networkObject.IsOwnershipSessionOwner && (!NetworkManager.LocalClient.IsSessionOwner || clientId != NetworkManager.CurrentSessionOwner)) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogErrorServer($"[{networkObject.name}][Session Owner Only] You cannot change ownership of a {nameof(NetworkObject)} that has the {NetworkObject.OwnershipStatus.SessionOwner} flag set!"); + } + networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly); + return; + } + // If are not authorized and this is not an approved ownership change, then check to see if we can change ownership if (!isAuthorized && !isRequestApproval) { @@ -771,7 +764,7 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ } /// - /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the + /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the /// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned. /// internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false) @@ -803,8 +796,8 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Prefab: { - // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the - // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically + // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the + // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically // but might want to use the same source network prefab as an in-scene placed NetworkObject. // (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically // spawning them so the original prefab placed is preserved and this is not needed) @@ -998,7 +991,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO /// - NetworkObject when spawning a newly instantiated NetworkObject for the first time. /// - NetworkSceneManager after a server/session-owner has loaded a scene to locally spawn the newly instantiated in-scene placed NetworkObjects. /// - NetworkSpawnManager when spawning any already loaded in-scene placed NetworkObjects (client-server or session owner). - /// + /// /// Client-Server: /// Server is the only instance that invokes this method. /// @@ -1376,7 +1369,7 @@ internal void DespawnAndDestroyNetworkObjects() } } - // If spawned, then despawn and potentially destroy. + // If spawned, then despawn and potentially destroy. if (networkObjects[i].IsSpawned) { OnDespawnObject(networkObjects[i], shouldDestroy); @@ -1746,6 +1739,11 @@ internal void GetObjectDistribution(ref Dictionary>()); + objectByTypeAndOwner.Add(globalOjectIdHash, new Dictionary>()); } // Sub-divide each type by owner - if (!objectByTypeAndOwner[networkObject.GlobalObjectIdHash].ContainsKey(networkObject.OwnerClientId)) + if (!objectByTypeAndOwner[globalOjectIdHash].ContainsKey(networkObject.OwnerClientId)) { - objectByTypeAndOwner[networkObject.GlobalObjectIdHash].Add(networkObject.OwnerClientId, new List()); + objectByTypeAndOwner[globalOjectIdHash].Add(networkObject.OwnerClientId, new List()); } // Add to the client's spawned object list - objectByTypeAndOwner[networkObject.GlobalObjectIdHash][networkObject.OwnerClientId].Add(networkObject); + objectByTypeAndOwner[globalOjectIdHash][networkObject.OwnerClientId].Add(networkObject); } } } @@ -1805,7 +1802,7 @@ internal void DistributeNetworkObjects(ulong clientId) } - // DA-NGO CMB SERVICE NOTES: + // DA-NGO CMB SERVICE NOTES: // The most basic object distribution should be broken up into a table of spawned object types // where each type contains a list of each client's owned objects of that type that can be // distributed. @@ -1824,7 +1821,7 @@ internal void DistributeNetworkObjects(ulong clientId) var clientCount = NetworkManager.ConnectedClientsIds.Count; - // Cycle through each prefab type + // Cycle through each prefab type foreach (var objectTypeEntry in distributedNetworkObjects) { // Calculate the number of objects that should be distributed amongst the clients @@ -1872,7 +1869,7 @@ internal void DistributeNetworkObjects(ulong clientId) if ((i % offsetCount) == 0) { ChangeOwnership(ownerList.Value[i], clientId, true); - if (EnableDistributeLogging) + //if (EnableDistributeLogging) { Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}"); } @@ -1894,7 +1891,7 @@ internal void DistributeNetworkObjects(ulong clientId) objectTypeCount.Clear(); GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); builder.AppendLine($"Client Relative Distributed Object Count: (distribution follows)"); - // Cycle through each prefab type + // Cycle through each prefab type foreach (var objectTypeEntry in distributedNetworkObjects) { builder.AppendLine($"[GID: {objectTypeEntry.Key} | {objectTypeEntry.Value.First().Value.First().name}][Total Count: {objectTypeCount[objectTypeEntry.Key]}]"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs index beccbdd9db..dcf44f7d8f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -128,7 +128,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() yield return WaitForConditionOrTimeOut(ValidateObjectSpawnedOnAllClients); AssertOnTimeout($"[Failed To Spawn] {firstInstance.name}: \n {m_ErrorLog}"); - // Validate the base non-assigned persmissions value for all instances are the same. + // Validate the base non-assigned permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -141,9 +141,15 @@ public IEnumerator ValidateOwnershipPermissionsTest() foreach (var permissionObject in Enum.GetValues(typeof(NetworkObject.OwnershipStatus))) { var permission = (NetworkObject.OwnershipStatus)permissionObject; + // Adding the SessionOwner flag here should fail as this NetworkObject is not owned by the Session Owner + if (permission == NetworkObject.OwnershipStatus.SessionOwner) + { + Assert.IsFalse(firstInstance.SetOwnershipStatus(permission), $"[Add][IncorrectPermissions] Setting {NetworkObject.OwnershipStatus.SessionOwner} is not valid when the client is not the Session Owner: \n {m_ErrorLog}!"); + continue; + } // Add the status firstInstance.SetOwnershipStatus(permission); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Add][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -153,7 +159,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() continue; } firstInstance.RemoveOwnershipStatus(permission); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Remove][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); } @@ -163,7 +169,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() firstInstance.SetOwnershipStatus(multipleFlags, true); Assert.IsTrue(firstInstance.HasOwnershipStatus(multipleFlags), $"[Set][Multi-flag Failure] Expected: {(ushort)multipleFlags} but was {(ushort)firstInstance.Ownership}!"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -175,7 +181,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Validate that the Distributable flag is still set Assert.IsTrue(firstInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Distributable), $"[Remove][Multi-flag Failure] Expected: {(ushort)NetworkObject.OwnershipStatus.Distributable} but was {(ushort)firstInstance.Ownership}!"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -186,7 +192,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Clear the flags, set the permissions to transferrable, and lock ownership in one pass. firstInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable, true, NetworkObject.OwnershipLockActions.SetAndLock); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Reset][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -199,7 +205,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() $" status is {secondInstanceHelper.OwnershipPermissionsFailureStatus}!"); firstInstance.SetOwnershipLock(false); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -208,7 +214,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Now try to acquire ownership secondInstance.ChangeOwnership(m_ClientNetworkManagers[1].LocalClientId); - // Validate the persmissions value for all instances are the same + // Validate the permissions value for all instances are the same yield return WaitForConditionOrTimeOut(() => secondInstance.IsOwner); AssertOnTimeout($"[Acquire Ownership Failed] Client-{m_ClientNetworkManagers[1].LocalClientId} failed to get ownership!"); @@ -220,7 +226,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Clear the flags, set the permissions to RequestRequired, and lock ownership in one pass. secondInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.RequestRequired, true); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); @@ -238,7 +244,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Start with a request for the client we expect to be given ownership var requestStatus = firstInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); // Get the 3rd client to send a request at the "relatively" same time var thirdInstance = m_ClientNetworkManagers[2].SpawnManager.SpawnedObjects[networkObjectId]; @@ -248,7 +254,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() requestStatus = thirdInstance.RequestOwnership(); // We expect the 3rd client's request should be able to be sent at this time as well (i.e. creates the race condition between two clients) - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{m_ServerNetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{m_ServerNetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); // We expect the first requesting client to be given ownership yield return WaitForConditionOrTimeOut(() => firstInstance.IsOwner); @@ -263,7 +269,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() yield return WaitForConditionOrTimeOut(() => thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress); AssertOnTimeout($"[Request In Progress Failed] Client-{thirdInstanceHelper.NetworkManager.LocalClientId} did not get the right request denied reponse!"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -278,11 +284,11 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Send out a request from three clients at the same time // The first one sent (and received for this test) gets ownership requestStatus = secondInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = thirdInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = fourthInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); // The 2nd and 3rd client should be denied and the 4th client should be approved yield return WaitForConditionOrTimeOut(() => @@ -296,7 +302,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() yield return WaitForConditionOrTimeOut(() => ValidateAllInstancesAreOwnedByClient(secondInstance.NetworkManager.LocalClientId)); AssertOnTimeout($"[Ownership Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); @@ -314,22 +320,84 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Send out a request from all three clients requestStatus = firstInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = thirdInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = fourthInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = daHostInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{daHostInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{daHostInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); - // The server and the 2nd client should be denied and the third client should be approved + // Only the client marked as ClientToAllowOwnership (daHost) should be approved. All others should be denied. yield return WaitForConditionOrTimeOut(() => (firstInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && (thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && (fourthInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && (daHostInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Approved) ); - AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request reponse: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!"); + AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request response: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!"); + + /////////////////////////////////////////////// + // Test OwnershipStatus.SessionOwner: + /////////////////////////////////////////////// + + OwnershipPermissionsTestHelper.CurrentOwnedInstance = daHostInstance; + m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance; + + // Add multiple statuses + daHostInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable | NetworkObject.OwnershipStatus.SessionOwner); + // Validate the permissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Add][Permissions Mismatch] {daHostInstance.name}: \n {m_ErrorLog}"); + + // Trying to set SessionOwner flag should override any other flags. + Assert.IsFalse(daHostInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Transferable), $"[Set][SessionOwner flag Failure] Expected: {NetworkObject.OwnershipStatus.Transferable} not to be set!"); + + // Add another status. Should fail as SessionOwner should be exclusive + daHostInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); + Assert.IsFalse(daHostInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Distributable), $"[Add][SessionOwner flag Failure] Expected: {NetworkObject.OwnershipStatus.Transferable} not to be set!"); + + // Request ownership of the SessionOwner flag instance + requestStatus = firstInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestRequiredNotSet, $"Client-{firstInstance.NetworkManager.LocalClientId} should not be able to send a request for ownership because object is marked as owned by the session owner. {requestStatus}!"); + + // Set ownership directly on local object. This will allow the request to be sent + firstInstance.Ownership = NetworkObject.OwnershipStatus.RequestRequired; + requestStatus = firstInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); + + // Request should be denied with CannotRequest + yield return WaitForConditionOrTimeOut(() => firstInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.CannotRequest); + AssertOnTimeout($"[Targeted Owner] Client-{firstInstance.NetworkManager.LocalClientId} did not get the right request response: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.CannotRequest}!"); + + // Try changing the ownership explicitly + // Get the cloned daHostInstance instance on a client side + var clientInstance = m_ClientNetworkManagers[2].SpawnManager.SpawnedObjects[daHostInstance.NetworkObjectId]; + + // Get the client instance of the OwnershipPermissionsTestHelper component + var clientInstanceHelper = clientInstance.GetComponent(); + + // Have the client attempt to change ownership + clientInstance.ChangeOwnership(m_ClientNetworkManagers[2].LocalClientId); + + // Verify the client side gets a permission failure status of NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly + Assert.IsTrue(clientInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly, + $"Expected {clientInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly} but its permission failure" + + $" status is {clientInstanceHelper.OwnershipPermissionsFailureStatus}!"); + + // Have the session owner attempt to change ownership to a non-session owner + daHostInstance.ChangeOwnership(m_ClientNetworkManagers[2].LocalClientId); + + // Verify the session owner cannot assign a SessionOwner permission NetworkObject to a non-sessionowner client + Assert.IsTrue(daHostInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly, + $"Expected {daHostInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly} but its permission failure" + + $" status is {daHostInstanceHelper.OwnershipPermissionsFailureStatus}!"); + + // Remove status + daHostInstance.RemoveOwnershipStatus(NetworkObject.OwnershipStatus.SessionOwner); + // Validate the permissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Remove][Permissions Mismatch] {daHostInstance.name}: \n {m_ErrorLog}"); } internal class OwnershipPermissionsTestHelper : NetworkBehaviour diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index bda9d15fcb..6c1fe6bba7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -127,7 +127,11 @@ public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChec } // Verifies that removing the ownership when the default (server) is already set does not cause a Key Not Found Exception - m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + // Distributed authority does not allow remove ownership (users are instructed to use change ownership) + if (!m_DistributedAuthority) + { + m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + } var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; var clientObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; @@ -237,7 +241,12 @@ bool WaitForClientsToSpawnNetworkObject() Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for all clients to change ownership!"); // Verifies that removing the ownership when the default (server) is already set does not cause a Key Not Found Exception - m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + // Distributed authority does not allow remove ownership (users are instructed to use change ownership) + if (!m_DistributedAuthority) + { + m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + } + var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; Assert.That(serverObject, Is.Not.Null); var clientObject = (NetworkObject)null; From 6de550bfee76ba014cd7ad37c7b0f7aa6c50230d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 13 Dec 2024 03:36:39 -0600 Subject: [PATCH 151/236] chore: merge develop 2 0 0 updates with SessionOwner permissions (#3176) * fix: clamp spawntimeout to recommended range (#3174) * update Clamping spawntimeout * update improving parenting failed message when either the child or parent NetworkObject is not spawnd. * update Update the local SceneEventData.SceneEventType on the authority side for SceneLoadComplete. * style removing whitespaces * feat: Add a SessionOwner ObjectStatus and allow InScenePlaced network objects to be distributed (#3175) * Initial pass on SessionOwner ownership flag * feat: Add SessionOwner OwnershipStatus flag * update removing additional session owner accessor. * Add OwnershipPermissions tests * fix client connect * Revert "fix client connect" This reverts commit 3c3b35444200da308dd07e1dd2e7b591fa0842d5. * update object distribution for in-scene placed NetworkObjects needs to use the InScenePlacedSourceGlobalObjectIdHash when building an object distribution list. * Add changelog * Remove unnecessary change * Remove Settings.json * Reword CHANGELOG * fix DAHost should not distribute session owner permission NetworkObjects. When client is promoted to session owner, for now newly promoted client takes ownership of NetworkObjects that have the SessionOwner permission set. Only prevent non-session owners from taking ownership of a NetworkObject with the SessionOwner permission set. * test fix Avoid the RemoveOwnership client-server only method. * style Visual studio code cleanup likes to sort by alpha... fixing for our standards. * update Adding check for session owner trying to change ownership to a non-session owner client. * test Adding an additional validation that a non-session owner cannot change ownership and that a session owner cannot change ownership to a non-session owner when the NetworkObject in question has the SessionOwner permissions set. --------- Co-authored-by: NoelStephensUnity --------- Co-authored-by: Emma --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Editor/NetworkObjectEditor.cs | 4 + .../Runtime/Configuration/NetworkConfig.cs | 25 ++++ .../Connection/NetworkConnectionManager.cs | 7 +- .../Runtime/Core/NetworkManager.cs | 9 +- .../Runtime/Core/NetworkObject.cs | 68 ++++++++--- .../SceneManagement/NetworkSceneManager.cs | 4 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 67 +++++------ .../OwnershipPermissionsTests.cs | 112 ++++++++++++++---- .../NetworkObjectOwnershipTests.cs | 13 +- 10 files changed, 231 insertions(+), 80 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index accae93b14..ff93d501ee 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `NetworkObject.OwnershipStatus.SessionOwner` to allow Network Objects to be distributable and only owned by the Session Owner. This flag will override all other `OwnershipStatus` flags. (#3175) - Added `UnityTransport.GetEndpoint` method to provide a way to obtain `NetworkEndpoint` information of a connection via client identifier. (#3130) - Added `NetworkTransport.OnEarlyUpdate` and `NetworkTransport.OnPostLateUpdate` methods to provide more control over handling transport related events at the start and end of each frame. (#3113) @@ -31,6 +32,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- In-scene placed `NetworkObject`s have been made distributable when balancing object distribution after a connection event. (#3175) - Optimised `NetworkVariable` and `NetworkTransform` related packets when in Distributed Authority mode. - The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 6adb7b8223..0860fd9c92 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -115,6 +115,10 @@ public override void OnInspectorGUI() EditorGUI.BeginChangeCheck(); serializedObject.UpdateIfRequiredOrScript(); DrawPropertiesExcluding(serializedObject, k_HiddenFields); + if (m_NetworkObject.IsOwnershipSessionOwner) + { + m_NetworkObject.Ownership = NetworkObject.OwnershipStatus.SessionOwner; + } serializedObject.ApplyModifiedProperties(); EditorGUI.EndChangeCheck(); diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 79035a895f..a9668084bd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -13,6 +13,14 @@ namespace Unity.Netcode [Serializable] public class NetworkConfig { + // Clamp spawn time outs to prevent dropping messages during scene events + // Note: The legacy versions of NGO defaulted to 1s which was too low. As + // well, the SpawnTimeOut is now being clamped to within this recommended + // range both via UI and when NetworkManager is validated. + internal const float MinSpawnTimeout = 10.0f; + // Clamp spawn time outs to no more than 1 hour (really that is a bit high) + internal const float MaxSpawnTimeout = 3600.0f; + /// /// The protocol version. Different versions doesn't talk to each other. /// @@ -132,6 +140,8 @@ public class NetworkConfig /// The amount of time a message will be held (deferred) if the destination NetworkObject needed to process the message doesn't exist yet. If the NetworkObject is not spawned within this time period, all deferred messages for that NetworkObject will be dropped. /// [Tooltip("The amount of time a message will be held (deferred) if the destination NetworkObject needed to process the message doesn't exist yet. If the NetworkObject is not spawned within this time period, all deferred messages for that NetworkObject will be dropped.")] + + [Range(MinSpawnTimeout, MaxSpawnTimeout)] public float SpawnTimeout = 10f; /// @@ -176,6 +186,21 @@ public class NetworkConfig [Tooltip("Enable (default) if you want to profile network messages with development builds and defaults to being disabled in release builds. When disabled, network messaging profiling will be disabled in development builds.")] public bool NetworkProfilingMetrics = true; + /// + /// Invoked by when it is validated. + /// + /// + /// Used to check for potential legacy values that have already been serialized and/or + /// runtime modifications to a property outside of the recommended range. + /// For each property checked below, provide a brief description of the reason. + /// + internal void OnValidate() + { + // Legacy NGO versions defaulted this value to 1 second that has since been determiend + // any range less than 10 seconds can lead to dropped messages during scene events. + SpawnTimeout = Mathf.Clamp(SpawnTimeout, MinSpawnTimeout, MaxSpawnTimeout); + } + /// /// Returns a base64 encoded version of the configuration /// diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index e297375ec1..fe6bc4816d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1212,7 +1212,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) { // Only NetworkObjects that have the OwnershipStatus.Distributable flag set and no parent // (ownership is transferred to all children) will have their ownership redistributed. - if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null) + if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null && !ownedObject.IsOwnershipSessionOwner) { if (ownedObject.IsOwnershipLocked) { @@ -1249,6 +1249,11 @@ internal void OnClientDisconnectFromServer(ulong clientId) childObject.SetOwnershipLock(false); } + // Ignore session owner marked objects + if (childObject.IsOwnershipSessionOwner) + { + continue; + } NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true); if (EnableDistributeLogging) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 75db75b9f2..9b3ef047a0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -225,11 +225,7 @@ internal void SetSessionOwner(ulong sessionOwner) foreach (var networkObjectEntry in SpawnManager.SpawnedObjects) { var networkObject = networkObjectEntry.Value; - if (networkObject.IsSceneObject == null || !networkObject.IsSceneObject.Value) - { - continue; - } - if (networkObject.OwnerClientId != LocalClientId) + if (networkObject.IsOwnershipSessionOwner && LocalClient.IsSessionOwner) { SpawnManager.ChangeOwnership(networkObject, LocalClientId, true); } @@ -955,6 +951,9 @@ internal void OnValidate() return; // May occur when the component is added } + // Do a validation pass on NetworkConfig properties + NetworkConfig.OnValidate(); + if (GetComponentInChildren() != null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 2ff4e27eb3..c449c9aadb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -439,6 +439,13 @@ public void DeferDespawn(int tickOffset, bool destroy = true) /// public bool IsOwnershipDistributable => Ownership.HasFlag(OwnershipStatus.Distributable); + /// + /// When true, the can only be owned by the current Session Owner. + /// To set during runtime, use to ensure the session owner owns the object. + /// Once the session owner owns the object, then use . + /// + public bool IsOwnershipSessionOwner => Ownership.HasFlag(OwnershipStatus.SessionOwner); + /// /// Returns true if the is has ownership locked. /// When locked, the cannot be redistributed nor can it be transferred by another client. @@ -481,7 +488,8 @@ public void DeferDespawn(int tickOffset, bool destroy = true) /// : If nothing is set, then ownership is considered "static" and cannot be redistributed, requested, or transferred (i.e. a Player would have this). /// : When set, this instance will be automatically redistributed when a client joins (if not locked or no request is pending) or leaves. /// : When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked). - /// : When set, When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). + /// : When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). + /// : When set, only the current session owner may have ownership over this object. /// // Ranges from 1 to 8 bits [Flags] @@ -491,6 +499,7 @@ public enum OwnershipStatus Distributable = 1 << 0, Transferable = 1 << 1, RequestRequired = 1 << 2, + SessionOwner = 1 << 3, } /// @@ -549,7 +558,7 @@ public bool SetOwnershipLock(bool lockOwnership = true) } // If we don't have the Transferable flag set and it is not a player object, then it is the same as having a static lock on ownership - if (!IsOwnershipTransferable && !IsPlayerObject) + if (!(IsOwnershipTransferable || IsPlayerObject) || IsOwnershipSessionOwner) { NetworkLog.LogWarning($"Trying to add or remove ownership lock on [{name}] which does not have the {nameof(OwnershipStatus.Transferable)} flag set!"); return false; @@ -582,13 +591,15 @@ public bool SetOwnershipLock(bool lockOwnership = true) /// : The requires an ownership request via . /// : The is already processing an ownership request and ownership cannot be acquired at this time. /// : The does not have the flag set and ownership cannot be acquired. + /// : The has the flag set and ownership cannot be acquired. /// public enum OwnershipPermissionsFailureStatus { Locked, RequestRequired, RequestInProgress, - NotTransferrable + NotTransferrable, + SessionOwnerOnly } /// @@ -610,6 +621,7 @@ public enum OwnershipPermissionsFailureStatus /// : The flag is not set on this /// : The current owner has locked ownership which means requests are not available at this time. /// : There is already a known request in progress. You can scan for ownership changes and try upon + /// : This object is marked as SessionOwnerOnly and therefore cannot be requested /// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership. /// public enum OwnershipRequestStatus @@ -619,6 +631,7 @@ public enum OwnershipRequestStatus RequestRequiredNotSet, Locked, RequestInProgress, + SessionOwnerOnly, } /// @@ -631,6 +644,7 @@ public enum OwnershipRequestStatus /// : The flag is not set on this /// : The current owner has locked ownership which means requests are not available at this time. /// : There is already a known request in progress. You can scan for ownership changes and try upon + /// : This object can only belong the the session owner and so cannot be requested /// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership. /// /// @@ -660,6 +674,12 @@ public OwnershipRequestStatus RequestOwnership() return OwnershipRequestStatus.RequestInProgress; } + // Exit early if it has the SessionOwner flag + if (IsOwnershipSessionOwner) + { + return OwnershipRequestStatus.SessionOwnerOnly; + } + // Otherwise, send the request ownership message var changeOwnership = new ChangeOwnershipMessage { @@ -716,7 +736,7 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) { response = OwnershipRequestResponseStatus.RequestInProgress; } - else if (!IsOwnershipRequestRequired && !IsOwnershipTransferable) + else if (!(IsOwnershipRequestRequired || IsOwnershipTransferable) || IsOwnershipSessionOwner) { response = OwnershipRequestResponseStatus.CannotRequest; } @@ -836,6 +856,12 @@ public enum OwnershipLockActions /// public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, OwnershipLockActions lockAction = OwnershipLockActions.None) { + if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManager.LocalClient.IsSessionOwner) + { + NetworkLog.LogWarning("Only the session owner is allowed to set the ownership status to session owner only."); + return false; + } + // If it already has the flag do nothing if (!clearAndSet && Ownership.HasFlag(status)) { @@ -847,13 +873,25 @@ public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, Ownership = OwnershipStatus.None; } - // Faster to just OR a None status than to check - // if it is !None before "OR'ing". - Ownership |= status; - - if (lockAction != OwnershipLockActions.None) + if (status.HasFlag(OwnershipStatus.SessionOwner)) { - SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock); + Ownership = OwnershipStatus.SessionOwner; + } + else if (Ownership.HasFlag(OwnershipStatus.SessionOwner)) + { + NetworkLog.LogWarning("No other ownership statuses may be set while SessionOwner is set."); + return false; + } + else + { + // Faster to just OR a None status than to check + // if it is !None before "OR'ing". + Ownership |= status; + + if (lockAction != OwnershipLockActions.None) + { + SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock); + } } SendOwnershipStatusUpdate(); @@ -1629,7 +1667,7 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla // DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set if (NetworkManager.LogLevel == LogLevel.Developer) { - NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will automatically set DistributeOwnership."); + NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will set ownership to SessionOwner."); } } } @@ -2007,12 +2045,14 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent != null && (IsSpawned ^ parent.IsSpawned)) + if (parent != null && (IsSpawned ^ parent.IsSpawned) && NetworkManager != null && !NetworkManager.ShutdownInProgress) { - if (NetworkManager != null && !NetworkManager.ShutdownInProgress) + if (NetworkManager.LogLevel <= LogLevel.Developer) { - return false; + var nameOfNotSpawnedObject = IsSpawned ? $" the parent ({parent.name})" : $"the child ({name})"; + NetworkLog.LogWarning($"Parenting failed because {nameOfNotSpawnedObject} is not spawned!"); } + return false; } m_CachedWorldPositionStays = worldPositionStays; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index acffc411f8..8996dfcfd7 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1903,10 +1903,12 @@ private void OnSessionOwnerLoadedScene(uint sceneEventId, Scene scene) SendSceneEventData(sceneEventData.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != sessionOwner).ToArray()); m_IsSceneEventActive = false; + + sceneEventData.SceneEventType = SceneEventType.LoadComplete; //First, notify local server that the scene was loaded OnSceneEvent?.Invoke(new SceneEvent() { - SceneEventType = SceneEventType.LoadComplete, + SceneEventType = sceneEventData.SceneEventType, LoadSceneMode = sceneEventData.LoadSceneMode, SceneName = SceneNameFromHash(sceneEventData.SceneHash), ClientId = NetworkManager.LocalClientId, diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 85ff9a89e2..ed0b0348b7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -422,27 +422,8 @@ internal void RemoveOwnership(NetworkObject networkObject) { if (NetworkManager.DistributedAuthorityMode && !NetworkManager.ShutdownInProgress) { - if (networkObject.IsOwnershipDistributable || networkObject.IsOwnershipTransferable) - { - if (networkObject.IsOwner || NetworkManager.DAHost) - { - NetworkLog.LogWarning("DANGO-TODO: Determine if removing ownership should make the CMB Service redistribute ownership or if this just isn't a valid thing in DAMode."); - return; - } - else - { - NetworkLog.LogError($"Only the owner is allowed to remove ownership in distributed authority mode!"); - return; - } - } - else - { - if (!NetworkManager.DAHost) - { - Debug.LogError($"Only {nameof(NetworkObject)}s with {nameof(NetworkObject.IsOwnershipDistributable)} or {nameof(NetworkObject.IsOwnershipTransferable)} set can perform ownership changes!"); - } - return; - } + Debug.LogError($"Removing ownership is invalid in Distributed Authority Mode. Use {nameof(ChangeOwnership)} instead."); + return; } ChangeOwnership(networkObject, NetworkManager.ServerClientId, true); } @@ -474,6 +455,18 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool if (NetworkManager.DistributedAuthorityMode) { + // Ensure only the session owner can change ownership (i.e. acquire) and that the session owner is not trying to assign a non-session owner client + // ownership of a NetworkObject with SessionOwner permissions. + if (networkObject.IsOwnershipSessionOwner && (!NetworkManager.LocalClient.IsSessionOwner || clientId != NetworkManager.CurrentSessionOwner)) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogErrorServer($"[{networkObject.name}][Session Owner Only] You cannot change ownership of a {nameof(NetworkObject)} that has the {NetworkObject.OwnershipStatus.SessionOwner} flag set!"); + } + networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly); + return; + } + // If are not authorized and this is not an approved ownership change, then check to see if we can change ownership if (!isAuthorized && !isRequestApproval) { @@ -1746,6 +1739,11 @@ internal void GetObjectDistribution(ref Dictionary>()); + objectByTypeAndOwner.Add(globalOjectIdHash, new Dictionary>()); } // Sub-divide each type by owner - if (!objectByTypeAndOwner[networkObject.GlobalObjectIdHash].ContainsKey(networkObject.OwnerClientId)) + if (!objectByTypeAndOwner[globalOjectIdHash].ContainsKey(networkObject.OwnerClientId)) { - objectByTypeAndOwner[networkObject.GlobalObjectIdHash].Add(networkObject.OwnerClientId, new List()); + objectByTypeAndOwner[globalOjectIdHash].Add(networkObject.OwnerClientId, new List()); } // Add to the client's spawned object list - objectByTypeAndOwner[networkObject.GlobalObjectIdHash][networkObject.OwnerClientId].Add(networkObject); + objectByTypeAndOwner[globalOjectIdHash][networkObject.OwnerClientId].Add(networkObject); } } } @@ -1872,7 +1869,7 @@ internal void DistributeNetworkObjects(ulong clientId) if ((i % offsetCount) == 0) { ChangeOwnership(ownerList.Value[i], clientId, true); - if (EnableDistributeLogging) + //if (EnableDistributeLogging) { Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}"); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs index beccbdd9db..dcf44f7d8f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -128,7 +128,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() yield return WaitForConditionOrTimeOut(ValidateObjectSpawnedOnAllClients); AssertOnTimeout($"[Failed To Spawn] {firstInstance.name}: \n {m_ErrorLog}"); - // Validate the base non-assigned persmissions value for all instances are the same. + // Validate the base non-assigned permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -141,9 +141,15 @@ public IEnumerator ValidateOwnershipPermissionsTest() foreach (var permissionObject in Enum.GetValues(typeof(NetworkObject.OwnershipStatus))) { var permission = (NetworkObject.OwnershipStatus)permissionObject; + // Adding the SessionOwner flag here should fail as this NetworkObject is not owned by the Session Owner + if (permission == NetworkObject.OwnershipStatus.SessionOwner) + { + Assert.IsFalse(firstInstance.SetOwnershipStatus(permission), $"[Add][IncorrectPermissions] Setting {NetworkObject.OwnershipStatus.SessionOwner} is not valid when the client is not the Session Owner: \n {m_ErrorLog}!"); + continue; + } // Add the status firstInstance.SetOwnershipStatus(permission); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Add][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -153,7 +159,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() continue; } firstInstance.RemoveOwnershipStatus(permission); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Remove][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); } @@ -163,7 +169,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() firstInstance.SetOwnershipStatus(multipleFlags, true); Assert.IsTrue(firstInstance.HasOwnershipStatus(multipleFlags), $"[Set][Multi-flag Failure] Expected: {(ushort)multipleFlags} but was {(ushort)firstInstance.Ownership}!"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -175,7 +181,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Validate that the Distributable flag is still set Assert.IsTrue(firstInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Distributable), $"[Remove][Multi-flag Failure] Expected: {(ushort)NetworkObject.OwnershipStatus.Distributable} but was {(ushort)firstInstance.Ownership}!"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Set Multiple][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -186,7 +192,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Clear the flags, set the permissions to transferrable, and lock ownership in one pass. firstInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable, true, NetworkObject.OwnershipLockActions.SetAndLock); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Reset][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -199,7 +205,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() $" status is {secondInstanceHelper.OwnershipPermissionsFailureStatus}!"); firstInstance.SetOwnershipLock(false); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -208,7 +214,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Now try to acquire ownership secondInstance.ChangeOwnership(m_ClientNetworkManagers[1].LocalClientId); - // Validate the persmissions value for all instances are the same + // Validate the permissions value for all instances are the same yield return WaitForConditionOrTimeOut(() => secondInstance.IsOwner); AssertOnTimeout($"[Acquire Ownership Failed] Client-{m_ClientNetworkManagers[1].LocalClientId} failed to get ownership!"); @@ -220,7 +226,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Clear the flags, set the permissions to RequestRequired, and lock ownership in one pass. secondInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.RequestRequired, true); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); @@ -238,7 +244,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Start with a request for the client we expect to be given ownership var requestStatus = firstInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); // Get the 3rd client to send a request at the "relatively" same time var thirdInstance = m_ClientNetworkManagers[2].SpawnManager.SpawnedObjects[networkObjectId]; @@ -248,7 +254,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() requestStatus = thirdInstance.RequestOwnership(); // We expect the 3rd client's request should be able to be sent at this time as well (i.e. creates the race condition between two clients) - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{m_ServerNetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{m_ServerNetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); // We expect the first requesting client to be given ownership yield return WaitForConditionOrTimeOut(() => firstInstance.IsOwner); @@ -263,7 +269,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() yield return WaitForConditionOrTimeOut(() => thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress); AssertOnTimeout($"[Request In Progress Failed] Client-{thirdInstanceHelper.NetworkManager.LocalClientId} did not get the right request denied reponse!"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); @@ -278,11 +284,11 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Send out a request from three clients at the same time // The first one sent (and received for this test) gets ownership requestStatus = secondInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = thirdInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = fourthInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); // The 2nd and 3rd client should be denied and the 4th client should be approved yield return WaitForConditionOrTimeOut(() => @@ -296,7 +302,7 @@ public IEnumerator ValidateOwnershipPermissionsTest() yield return WaitForConditionOrTimeOut(() => ValidateAllInstancesAreOwnedByClient(secondInstance.NetworkManager.LocalClientId)); AssertOnTimeout($"[Ownership Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); - // Validate the persmissions value for all instances are the same. + // Validate the permissions value for all instances are the same. yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); AssertOnTimeout($"[Unlock][Permissions Mismatch] {secondInstance.name}: \n {m_ErrorLog}"); @@ -314,22 +320,84 @@ public IEnumerator ValidateOwnershipPermissionsTest() // Send out a request from all three clients requestStatus = firstInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = thirdInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{thirdInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = fourthInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{fourthInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); requestStatus = daHostInstance.RequestOwnership(); - Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{daHostInstance.NetworkManager.LocalClientId} was unabled to send a request for ownership because: {requestStatus}!"); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{daHostInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); - // The server and the 2nd client should be denied and the third client should be approved + // Only the client marked as ClientToAllowOwnership (daHost) should be approved. All others should be denied. yield return WaitForConditionOrTimeOut(() => (firstInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && (thirdInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && (fourthInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied) && (daHostInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Approved) ); - AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request reponse: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!"); + AssertOnTimeout($"[Targeted Owner] Client-{daHostInstance.NetworkManager.LocalClientId} did not get the right request response: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.Approved}!"); + + /////////////////////////////////////////////// + // Test OwnershipStatus.SessionOwner: + /////////////////////////////////////////////// + + OwnershipPermissionsTestHelper.CurrentOwnedInstance = daHostInstance; + m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance; + + // Add multiple statuses + daHostInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable | NetworkObject.OwnershipStatus.SessionOwner); + // Validate the permissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Add][Permissions Mismatch] {daHostInstance.name}: \n {m_ErrorLog}"); + + // Trying to set SessionOwner flag should override any other flags. + Assert.IsFalse(daHostInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Transferable), $"[Set][SessionOwner flag Failure] Expected: {NetworkObject.OwnershipStatus.Transferable} not to be set!"); + + // Add another status. Should fail as SessionOwner should be exclusive + daHostInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); + Assert.IsFalse(daHostInstance.HasOwnershipStatus(NetworkObject.OwnershipStatus.Distributable), $"[Add][SessionOwner flag Failure] Expected: {NetworkObject.OwnershipStatus.Transferable} not to be set!"); + + // Request ownership of the SessionOwner flag instance + requestStatus = firstInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestRequiredNotSet, $"Client-{firstInstance.NetworkManager.LocalClientId} should not be able to send a request for ownership because object is marked as owned by the session owner. {requestStatus}!"); + + // Set ownership directly on local object. This will allow the request to be sent + firstInstance.Ownership = NetworkObject.OwnershipStatus.RequestRequired; + requestStatus = firstInstance.RequestOwnership(); + Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{firstInstance.NetworkManager.LocalClientId} was unable to send a request for ownership because: {requestStatus}!"); + + // Request should be denied with CannotRequest + yield return WaitForConditionOrTimeOut(() => firstInstanceHelper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.CannotRequest); + AssertOnTimeout($"[Targeted Owner] Client-{firstInstance.NetworkManager.LocalClientId} did not get the right request response: {daHostInstanceHelper.OwnershipRequestResponseStatus} Expecting: {NetworkObject.OwnershipRequestResponseStatus.CannotRequest}!"); + + // Try changing the ownership explicitly + // Get the cloned daHostInstance instance on a client side + var clientInstance = m_ClientNetworkManagers[2].SpawnManager.SpawnedObjects[daHostInstance.NetworkObjectId]; + + // Get the client instance of the OwnershipPermissionsTestHelper component + var clientInstanceHelper = clientInstance.GetComponent(); + + // Have the client attempt to change ownership + clientInstance.ChangeOwnership(m_ClientNetworkManagers[2].LocalClientId); + + // Verify the client side gets a permission failure status of NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly + Assert.IsTrue(clientInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly, + $"Expected {clientInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly} but its permission failure" + + $" status is {clientInstanceHelper.OwnershipPermissionsFailureStatus}!"); + + // Have the session owner attempt to change ownership to a non-session owner + daHostInstance.ChangeOwnership(m_ClientNetworkManagers[2].LocalClientId); + + // Verify the session owner cannot assign a SessionOwner permission NetworkObject to a non-sessionowner client + Assert.IsTrue(daHostInstanceHelper.OwnershipPermissionsFailureStatus == NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly, + $"Expected {daHostInstance.name} to return {NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly} but its permission failure" + + $" status is {daHostInstanceHelper.OwnershipPermissionsFailureStatus}!"); + + // Remove status + daHostInstance.RemoveOwnershipStatus(NetworkObject.OwnershipStatus.SessionOwner); + // Validate the permissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Remove][Permissions Mismatch] {daHostInstance.name}: \n {m_ErrorLog}"); } internal class OwnershipPermissionsTestHelper : NetworkBehaviour diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index bda9d15fcb..6c1fe6bba7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -127,7 +127,11 @@ public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChec } // Verifies that removing the ownership when the default (server) is already set does not cause a Key Not Found Exception - m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + // Distributed authority does not allow remove ownership (users are instructed to use change ownership) + if (!m_DistributedAuthority) + { + m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + } var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; var clientObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; @@ -237,7 +241,12 @@ bool WaitForClientsToSpawnNetworkObject() Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for all clients to change ownership!"); // Verifies that removing the ownership when the default (server) is already set does not cause a Key Not Found Exception - m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + // Distributed authority does not allow remove ownership (users are instructed to use change ownership) + if (!m_DistributedAuthority) + { + m_ServerNetworkManager.SpawnManager.RemoveOwnership(m_OwnershipNetworkObject); + } + var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; Assert.That(serverObject, Is.Not.Null); var clientObject = (NetworkObject)null; From 1a2a6279b9adfa25ea761c9aed3776f5aeff32cf Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 14 Dec 2024 16:12:22 -0600 Subject: [PATCH 152/236] chore: adding overriding scenes and prefabs example (#3161) * update Adding the initial scene and prefab override example * update Several modifications to handle distributed authority integration with this example. * update adding temporary package * update adding manifest * update deleting packages * add Readme Adding a work in progress readme file * style Adding additional LB after images * Update Adding some additional links * update Finalized readme and migrated images to local directory. Cleaned up some minor issues including renaming of some properties, handling resetting of camera when running in server mode and a client being followed disconnects, and some other minor clean up. --- .../OverridingScenesAndPrefabs/.gitignore | 83 + .../Assets/DefaultNetworkPrefabs.asset | 46 + .../Assets/DefaultNetworkPrefabs.asset.meta | 8 + .../Assets/Materials.meta | 8 + .../Assets/Materials/ChildLocal.mat | 86 ++ .../Assets/Materials/ChildLocal.mat.meta | 8 + .../Materials/ChildLocalNoParentSync.mat | 86 ++ .../Materials/ChildLocalNoParentSync.mat.meta | 8 + .../Assets/Materials/ChildWorld.mat | 86 ++ .../Assets/Materials/ChildWorld.mat.meta | 8 + .../Materials/ChildWorldNoParentSync.mat | 86 ++ .../Materials/ChildWorldNoParentSync.mat.meta | 8 + .../Assets/Materials/Direction.mat | 84 ++ .../Assets/Materials/Direction.mat.meta | 8 + .../Assets/Materials/GridBlue.mat | 138 ++ .../Assets/Materials/GridBlue.mat.meta | 8 + .../Assets/Materials/GridOrange.mat | 138 ++ .../Assets/Materials/GridOrange.mat.meta | 8 + .../Assets/Materials/GridPattern.mat | 85 ++ .../Assets/Materials/GridPattern.mat.meta | 8 + .../Assets/Materials/GridWhite.mat | 138 ++ .../Assets/Materials/GridWhite.mat.meta | 8 + .../Assets/Materials/Ground.mat | 79 + .../Assets/Materials/Ground.mat.meta | 8 + .../Assets/Materials/PlayerMaterial.mat | 84 ++ .../Assets/Materials/PlayerMaterial.mat.meta | 8 + .../Materials/ShaderGraphGrid_01_Mat.mat | 134 ++ .../Materials/ShaderGraphGrid_01_Mat.mat.meta | 8 + .../Assets/Models.meta | 8 + .../Assets/Models/Ramp_100x100x200_Mesh.fbx | Bin 0 -> 15712 bytes .../Models/Ramp_100x100x200_Mesh.fbx.meta | 107 ++ .../Assets/Models/Ramp_Mesh.fbx | Bin 0 -> 42320 bytes .../Assets/Models/Ramp_Mesh.fbx.meta | 107 ++ .../Assets/Models/Tunnel_Mesh.fbx | Bin 0 -> 45056 bytes .../Assets/Models/Tunnel_Mesh.fbx.meta | 107 ++ .../Assets/Models/Wall_Mesh.fbx | Bin 0 -> 45440 bytes .../Assets/Models/Wall_Mesh.fbx.meta | 107 ++ .../Assets/Prefabs.meta | 8 + .../Assets/Prefabs/ClientPlayer.prefab | 468 ++++++ .../Assets/Prefabs/ClientPlayer.prefab.meta | 7 + .../Assets/Prefabs/Floor.physicMaterial | 15 + .../Assets/Prefabs/Floor.physicMaterial.meta | 8 + .../Assets/Prefabs/Ramp_Prefab.prefab | 111 ++ .../Assets/Prefabs/Ramp_Prefab.prefab.meta | 7 + .../Prefabs/RotatingBody.physicMaterial | 15 + .../Prefabs/RotatingBody.physicMaterial.meta | 8 + .../Assets/Prefabs/RotatingBody.prefab | 1138 ++++++++++++++ .../Assets/Prefabs/RotatingBody.prefab.meta | 7 + .../Assets/Prefabs/SceneLevelGeometry.prefab | 794 ++++++++++ .../Prefabs/SceneLevelGeometry.prefab.meta | 7 + .../Assets/Prefabs/ServerPlayer.prefab | 656 ++++++++ .../Assets/Prefabs/ServerPlayer.prefab.meta | 7 + .../Assets/Prefabs/Tunnel_Prefab.prefab | 154 ++ .../Assets/Prefabs/Tunnel_Prefab.prefab.meta | 7 + .../Assets/Prefabs/Wall_Prefab.prefab | 132 ++ .../Assets/Prefabs/Wall_Prefab.prefab.meta | 7 + .../Assets/Resources.meta | 8 + .../Assets/Resources/BillingMode.json | 1 + .../Assets/Resources/BillingMode.json.meta | 7 + .../Assets/Scenes.meta | 8 + .../Assets/Scenes/BootstrapScene.unity | 282 ++++ .../Assets/Scenes/BootstrapScene.unity.meta | 7 + .../Assets/Scenes/Camera.preset | 195 +++ .../Assets/Scenes/Camera.preset.meta | 8 + .../Assets/Scenes/ClientScene.unity | 382 +++++ .../Assets/Scenes/ClientScene.unity.meta | 7 + .../Assets/Scenes/MainMenu.unity | 607 ++++++++ .../Assets/Scenes/MainMenu.unity.meta | 7 + .../Assets/Scenes/ProjectPrefabs.asset | 26 + .../Assets/Scenes/ProjectPrefabs.asset.meta | 8 + .../Assets/Scenes/ServerScene.unity | 402 +++++ .../Assets/Scenes/ServerScene.unity.meta | 7 + .../Assets/Scenes/SharedScene.unity | 1343 +++++++++++++++++ .../Assets/Scenes/SharedScene.unity.meta | 7 + .../Assets/Scripts.meta | 8 + .../Scripts/InstanceTypeLocalBehavior.cs | 108 ++ .../Scripts/InstanceTypeLocalBehavior.cs.meta | 2 + .../Assets/Scripts/MoverScriptNoRigidbody.cs | 376 +++++ .../Scripts/MoverScriptNoRigidbody.cs.meta | 2 + .../Scripts/NetworkManagerBootstrapper.cs | 527 +++++++ .../NetworkManagerBootstrapper.cs.meta | 11 + .../Scripts/NetworkPrefabOverrideHandler.cs | 65 + .../NetworkPrefabOverrideHandler.cs.meta | 2 + .../Assets/Scripts/PlayerBallMotion.cs | 126 ++ .../Assets/Scripts/PlayerBallMotion.cs.meta | 2 + .../Assets/Scripts/PlayerColor.cs | 47 + .../Assets/Scripts/PlayerColor.cs.meta | 11 + .../Assets/Scripts/RotatingBodyLogic.cs | 201 +++ .../Assets/Scripts/RotatingBodyLogic.cs.meta | 2 + .../Assets/Scripts/SceneBootstrapLoader.cs | 417 +++++ .../Scripts/SceneBootstrapLoader.cs.meta | 2 + .../Assets/Scripts/ServerHostClientText.cs | 85 ++ .../Scripts/ServerHostClientText.cs.meta | 11 + .../Assets/Scripts/ServerInfoDisplay.cs | 27 + .../Assets/Scripts/ServerInfoDisplay.cs.meta | 2 + .../Assets/Scripts/TriggerPush.cs | 63 + .../Assets/Scripts/TriggerPush.cs.meta | 2 + .../Assets/Textures.meta | 8 + .../Assets/Textures/GridPattern.png | Bin 0 -> 3152344 bytes .../Assets/Textures/GridPattern.png.meta | 153 ++ .../Assets/Textures/Grid_01_BaseMap.png | Bin 0 -> 8210 bytes .../Assets/Textures/Grid_01_BaseMap.png.meta | 130 ++ .../Assets/Textures/Grid_01_Emissive.png | Bin 0 -> 8225 bytes .../Assets/Textures/Grid_01_Emissive.png.meta | 130 ++ .../Assets/Textures/Grid_01_Normal.png | Bin 0 -> 21103 bytes .../Assets/Textures/Grid_01_Normal.png.meta | 130 ++ .../Assets/Textures/Grid_02_BaseMap.png | Bin 0 -> 9489 bytes .../Assets/Textures/Grid_02_BaseMap.png.meta | 130 ++ .../Images/EndResultView.png | Bin 0 -> 383115 bytes .../Images/NetworkManagerBootstrapper.png | Bin 0 -> 41553 bytes .../NetworkManagerBootstrapperProperties.png | Bin 0 -> 56002 bytes .../Images/NetworkPrefabOverrideHandler.png | Bin 0 -> 41209 bytes .../Images/SceneBootstrapLoader.png | Bin 0 -> 29100 bytes .../Images/ScenesView.png | Bin 0 -> 18823 bytes .../Images/ServiceSettings.png | Bin 0 -> 71789 bytes .../Packages/manifest.json | 56 + .../ProjectSettings/ClusterInputManager.asset | 6 + .../ProjectSettings/DynamicsManager.asset | 37 + .../ProjectSettings/EditorBuildSettings.asset | 24 + .../ProjectSettings/EditorSettings.asset | 48 + .../ProjectSettings/GraphicsSettings.asset | 66 + .../ProjectSettings/InputManager.asset | 296 ++++ .../ProjectSettings/MemorySettings.asset | 35 + .../ProjectSettings/MultiplayerManager.asset | 7 + .../ProjectSettings/NavMeshAreas.asset | 93 ++ .../ProjectSettings/Physics2DSettings.asset | 56 + .../ProjectSettings/PresetManager.asset | 7 + .../ProjectSettings/ProjectSettings.asset | 782 ++++++++++ .../ProjectSettings/QualitySettings.asset | 340 +++++ .../ProjectSettings/TagManager.asset | 45 + .../ProjectSettings/TimeManager.asset | 13 + .../UnityConnectSettings.asset | 36 + .../ProjectSettings/VFXManager.asset | 19 + .../VersionControlSettings.asset | 7 + Examples/OverridingScenesAndPrefabs/Readme.md | 112 ++ 135 files changed, 13634 insertions(+) create mode 100644 Examples/OverridingScenesAndPrefabs/.gitignore create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_100x100x200_Mesh.fbx create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_100x100x200_Mesh.fbx.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_Mesh.fbx create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_Mesh.fbx.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Tunnel_Mesh.fbx create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Tunnel_Mesh.fbx.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Wall_Mesh.fbx create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Models/Wall_Mesh.fbx.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Resources.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/GridPattern.png create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/GridPattern.png.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_BaseMap.png create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_BaseMap.png.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Emissive.png create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Emissive.png.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Normal.png create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Normal.png.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_02_BaseMap.png create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_02_BaseMap.png.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Images/EndResultView.png create mode 100644 Examples/OverridingScenesAndPrefabs/Images/NetworkManagerBootstrapper.png create mode 100644 Examples/OverridingScenesAndPrefabs/Images/NetworkManagerBootstrapperProperties.png create mode 100644 Examples/OverridingScenesAndPrefabs/Images/NetworkPrefabOverrideHandler.png create mode 100644 Examples/OverridingScenesAndPrefabs/Images/SceneBootstrapLoader.png create mode 100644 Examples/OverridingScenesAndPrefabs/Images/ScenesView.png create mode 100644 Examples/OverridingScenesAndPrefabs/Images/ServiceSettings.png create mode 100644 Examples/OverridingScenesAndPrefabs/Packages/manifest.json create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/ClusterInputManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/DynamicsManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/EditorBuildSettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/EditorSettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/GraphicsSettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/InputManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/MemorySettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/MultiplayerManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/NavMeshAreas.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/Physics2DSettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/PresetManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/ProjectSettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/QualitySettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/TagManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/TimeManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/UnityConnectSettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/VFXManager.asset create mode 100644 Examples/OverridingScenesAndPrefabs/ProjectSettings/VersionControlSettings.asset create mode 100644 Examples/OverridingScenesAndPrefabs/Readme.md diff --git a/Examples/OverridingScenesAndPrefabs/.gitignore b/Examples/OverridingScenesAndPrefabs/.gitignore new file mode 100644 index 0000000000..2800399634 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/.gitignore @@ -0,0 +1,83 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore +# +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Asset meta data should only be ignored when the corresponding asset is also ignored +!/[Aa]ssets/**/*.meta + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.aab +*.unitypackage + +# Crashlytics generated file +crashlytics-build.properties + +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json +/[Aa]ssets/[Ss]treamingAssets/BuildInfo.json.meta + +# Secrets +*.pem +*.pem.meta + +InitTestScene* + +boot.config +SceneTemplateSettings.json +*BurstAotSettings*.json diff --git a/Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset b/Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset new file mode 100644 index 0000000000..56426e31b7 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset @@ -0,0 +1,46 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e651dbb3fbac04af2b8f5abf007ddc23, type: 3} + m_Name: DefaultNetworkPrefabs + m_EditorClassIdentifier: + IsDefault: 1 + List: + - Override: 0 + Prefab: {fileID: 8921789205124766477, guid: a279f80620d4e6a4ab02c1a9c96ff460, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 8921789205124766477, guid: 8ae02ac62e2067144b8ff06d48aeb47a, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 3439633038736912633, guid: 398aad09d8b2a47eba664a076763cdcc, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 6472733969592893139, guid: 3e5167b6e6bcb5645abb2dbc0078091e, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset.meta b/Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset.meta new file mode 100644 index 0000000000..fee27b3ade --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/DefaultNetworkPrefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aa82390bfdde2564f828b8e5be375282 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials.meta new file mode 100644 index 0000000000..463de70d61 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d5b7ad71451c27e4291295cfffc10328 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat new file mode 100644 index 0000000000..b6fd2dd9a1 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildLocal + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.8980392, g: 0.039215658, b: 0.7682729, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat.meta new file mode 100644 index 0000000000..35e4d565be --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocal.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45fc555bc05bfee4ab8b0d536799ecee +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat new file mode 100644 index 0000000000..c44172e7a7 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildLocalNoParentSync + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.039215658, g: 0.78592235, b: 0.8980392, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat.meta new file mode 100644 index 0000000000..3a26e43d98 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildLocalNoParentSync.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: affef70511a06dd46b8f52636020af4a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat new file mode 100644 index 0000000000..0c2bf4b187 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildWorld + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.3783494, g: 0.039215658, b: 0.8980392, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat.meta new file mode 100644 index 0000000000..9a00ded8c6 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorld.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15d0bda12a233964086aee5c0c357e24 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat new file mode 100644 index 0000000000..86b27eb5c9 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ChildWorldNoParentSync + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _ALPHAPREMULTIPLY_ON + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 3 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 0.039215658, g: 0.8980392, b: 0.09095798, a: 0.2509804} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat.meta new file mode 100644 index 0000000000..5ab7ff2e72 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ChildWorldNoParentSync.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a5e199307b2e0894294d9c8bee99a691 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat new file mode 100644 index 0000000000..ed5ed117c3 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat @@ -0,0 +1,84 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Direction + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 0.8962264, g: 0.52830994, b: 0.038047332, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat.meta new file mode 100644 index 0000000000..c93791fac0 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Direction.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5324c76c2bab7344badd5ea27a40bcb5 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat new file mode 100644 index 0000000000..9009817e26 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat @@ -0,0 +1,138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-2624353793879203111 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridBlue + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _EMISSION + - _GLOSSYREFLECTIONS_OFF + - _NORMALMAP + m_InvalidKeywords: [] + m_LightmapFlags: 2 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: b94463ba36040ec4082132c54dd1bbad, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 8bf2cf149563066489e749ea032dbca7, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: e71f43865e91e6b418bd0d67be2445dc, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 1 + - _Glossiness: 0.477 + - _GlossyReflections: 0 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _UVSec: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 0.5438323, g: 0.88474977, b: 0.9528302, a: 1} + - _EmissionColor: {r: 0, g: 0.6988592, b: 1, a: 1} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat.meta new file mode 100644 index 0000000000..b8caf3cbac --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridBlue.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1448a5da57523ce4bbc377143e02fe3c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat new file mode 100644 index 0000000000..38a4e9808b --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat @@ -0,0 +1,138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-2624353793879203111 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridOrange + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _EMISSION + - _GLOSSYREFLECTIONS_OFF + - _NORMALMAP + m_InvalidKeywords: [] + m_LightmapFlags: 2 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: b94463ba36040ec4082132c54dd1bbad, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 8bf2cf149563066489e749ea032dbca7, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: e71f43865e91e6b418bd0d67be2445dc, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 1 + - _Glossiness: 0.477 + - _GlossyReflections: 0 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _UVSec: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 1, g: 0.49475378, b: 0, a: 1} + - _EmissionColor: {r: 0.990566, g: 0.48359674, b: 0.378471, a: 1} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat.meta new file mode 100644 index 0000000000..bf2873069e --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridOrange.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2e2c886f4af8e304eb9a1e2e50d023b3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat new file mode 100644 index 0000000000..f44981f387 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat @@ -0,0 +1,85 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridPattern + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _GLOSSYREFLECTIONS_OFF + - _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 1 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 6, y: 6} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: a092c5fa8c60ed04aa1d72555f1740bc, type: 3} + m_Scale: {x: 6, y: 6} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0.785 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 1 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0.254717, g: 0.23188858, b: 0.23188858, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat.meta new file mode 100644 index 0000000000..cbca0f4485 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridPattern.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 42c4a0ad1f9d67a45b12f68697321aad +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat new file mode 100644 index 0000000000..6e1b52a2c0 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat @@ -0,0 +1,138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-2624353793879203111 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GridWhite + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _EMISSION + - _GLOSSYREFLECTIONS_OFF + - _NORMALMAP + m_InvalidKeywords: [] + m_LightmapFlags: 2 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: b94463ba36040ec4082132c54dd1bbad, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 8bf2cf149563066489e749ea032dbca7, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0.03} + - _MainTex: + m_Texture: {fileID: 2800000, guid: 844faea9c35b2464c8ecb09f66e2e2f8, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0.03} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0.24 + - _Glossiness: 0.846 + - _GlossyReflections: 0 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _UVSec: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 1, g: 1, b: 1, a: 1} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat.meta new file mode 100644 index 0000000000..4559482bba --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/GridWhite.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a53ba8919fa78c14caac473c7e7ce7d3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat new file mode 100644 index 0000000000..252ea1a0ed --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat @@ -0,0 +1,79 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Ground + m_Shader: {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + m_ShaderKeywords: + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 0.14769986, g: 0.1509434, b: 0.1473834, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat.meta new file mode 100644 index 0000000000..6dbcac16d3 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/Ground.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9c73b921ea39f4344a19c2d1c7d6b314 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat new file mode 100644 index 0000000000..bc67e92539 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat @@ -0,0 +1,84 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: PlayerMaterial + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat.meta new file mode 100644 index 0000000000..1ceca58536 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/PlayerMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 44e292334941fe148b997ca2b01b5789 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat new file mode 100644 index 0000000000..5161ac9b8e --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat @@ -0,0 +1,134 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ShaderGraphGrid_01_Mat + m_Shader: {fileID: -6465566751694194690, guid: b8d7679189d4a5940af46004f3870920, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - Texture2D_C5E3E723: + m_Texture: {fileID: 2800000, guid: d4d6919451fe3e24388816386a6d15a4, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _Grid_Normal_Map: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: d9c0dd5cdac07b145be73329e489869a, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0.004, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - Boolean_35A71344: 0 + - Boolean_7A5F3F39: 1 + - Boolean_7AC8D832: 1 + - Vector1_3402D67A: 1 + - Vector1_3655428E: 5 + - Vector1_5B05FA1F: 0.062 + - Vector1_6B67A8FF: -20 + - Vector1_7810F718: 1 + - Vector1_B6126E6E: 0.335 + - Vector1_CA7D5F3: 30 + - Vector1_D5FBE925: 0.3 + - Vector1_F2A922B4: 1.73 + - Vector1_F5FD9210: 33.9 + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.119 + - _GlossyReflections: 1 + - _Grid_Normal_Strength: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 0 + - _Parallax: 0.02 + - _QueueControl: 0 + - _QueueOffset: 0 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - Color_2B671050: {r: 0.23202202, g: 0.6245157, b: 0.745283, a: 0} + - Color_30A0CA2F: {r: 0.02745098, g: 1, b: 0.7565653, a: 0} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] +--- !u!114 &6450197988115792188 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 7 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta new file mode 100644 index 0000000000..efd03db61f --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Materials/ShaderGraphGrid_01_Mat.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7be68f3cac640fd40a7663ac97380a9c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Models.meta b/Examples/OverridingScenesAndPrefabs/Assets/Models.meta new file mode 100644 index 0000000000..21910d8d7a --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Models.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd50c879f39d9d94b92706513b4f56ef +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_100x100x200_Mesh.fbx b/Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_100x100x200_Mesh.fbx new file mode 100644 index 0000000000000000000000000000000000000000..2fa479b052cab2b81a3182ab6919d00774145096 GIT binary patch literal 15712 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 literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_Mesh.fbx.meta b/Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_Mesh.fbx.meta new file mode 100644 index 0000000000..f9aabd0c03 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Models/Ramp_Mesh.fbx.meta @@ -0,0 +1,107 @@ +fileFormatVersion: 2 +guid: 5d8449220d5795b448a4cee5cbde4b56 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Models/Tunnel_Mesh.fbx b/Examples/OverridingScenesAndPrefabs/Assets/Models/Tunnel_Mesh.fbx new file mode 100644 index 0000000000000000000000000000000000000000..7ec9d4bb74a7b846a5ad5f702842793d40fe839d GIT binary patch literal 45056 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 z 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 literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Models/Wall_Mesh.fbx.meta b/Examples/OverridingScenesAndPrefabs/Assets/Models/Wall_Mesh.fbx.meta new file mode 100644 index 0000000000..15452589e0 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Models/Wall_Mesh.fbx.meta @@ -0,0 +1,107 @@ +fileFormatVersion: 2 +guid: 34560503bf9d61046b252db98a8cf770 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs.meta new file mode 100644 index 0000000000..c867cd3a70 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4b97118ef11ec3347bc72b8d681e094b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab new file mode 100644 index 0000000000..78723f0b08 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab @@ -0,0 +1,468 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1001 &1768978093373392434 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_Name + value: ClientPlayer + objectReference: {fileID: 0} + - target: {fileID: 5488962521685982563, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: GlobalObjectIdHash + value: 3304021165 + objectReference: {fileID: 0} + - target: {fileID: 6582615468703263770, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: GlobalObjectIdHash + value: 3304021165 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7209693128479548322, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: -1 + addedObject: {fileID: 916832828106534790} + - targetCorrespondingSourceObject: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: -1 + addedObject: {fileID: 4315296084954952915} + - targetCorrespondingSourceObject: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: -1 + addedObject: {fileID: 8226639525103180092} + - targetCorrespondingSourceObject: {fileID: 3385537716926926172, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 1 + addedObject: {fileID: 1499413778802221178} + - targetCorrespondingSourceObject: {fileID: 3385537716926926172, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 2 + addedObject: {fileID: 3977729948622378772} + - targetCorrespondingSourceObject: {fileID: 9153294333356437960, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 1 + addedObject: {fileID: 1444122194983607567} + - targetCorrespondingSourceObject: {fileID: 9153294333356437960, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 2 + addedObject: {fileID: 2160506865115608745} + - targetCorrespondingSourceObject: {fileID: 324680519892629439, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 1 + addedObject: {fileID: 204575060996785653} + - targetCorrespondingSourceObject: {fileID: 324680519892629439, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 2 + addedObject: {fileID: 544477718306327008} + - targetCorrespondingSourceObject: {fileID: 1574061277261592297, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 1 + addedObject: {fileID: 8023734133310089212} + - targetCorrespondingSourceObject: {fileID: 1574061277261592297, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 2 + addedObject: {fileID: 5358980426924378504} + - targetCorrespondingSourceObject: {fileID: 7170742296219383225, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 1 + addedObject: {fileID: 8116266233139283823} + - targetCorrespondingSourceObject: {fileID: 7170742296219383225, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + insertIndex: 2 + addedObject: {fileID: 6061467578651748175} + m_SourcePrefab: {fileID: 100100000, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} +--- !u!1 &960564489074766043 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 1574061277261592297, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + m_PrefabInstance: {fileID: 1768978093373392434} + m_PrefabAsset: {fileID: 0} +--- !u!33 &8023734133310089212 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 960564489074766043} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &5358980426924378504 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 960564489074766043} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a53ba8919fa78c14caac473c7e7ce7d3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &2021503815370961293 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 324680519892629439, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + m_PrefabInstance: {fileID: 1768978093373392434} + m_PrefabAsset: {fileID: 0} +--- !u!33 &204575060996785653 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2021503815370961293} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &544477718306327008 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2021503815370961293} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a53ba8919fa78c14caac473c7e7ce7d3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &3924733872325380974 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 3385537716926926172, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + m_PrefabInstance: {fileID: 1768978093373392434} + m_PrefabAsset: {fileID: 0} +--- !u!33 &1499413778802221178 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3924733872325380974} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &3977729948622378772 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3924733872325380974} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 5324c76c2bab7344badd5ea27a40bcb5, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &6472733969592893139 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + m_PrefabInstance: {fileID: 1768978093373392434} + m_PrefabAsset: {fileID: 0} +--- !u!114 &916832828106534790 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6472733969592893139} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bb9a9987a689bbe43a05463cf3713b22, type: 3} + m_Name: + m_EditorClassIdentifier: + ServerOnly: 0 + UniqueLocalInstanceContent: ' Local content specific to ClientPlayer' +--- !u!33 &4315296084954952915 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6472733969592893139} + m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &8226639525103180092 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6472733969592893139} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a53ba8919fa78c14caac473c7e7ce7d3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &7461238108046391290 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 9153294333356437960, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + m_PrefabInstance: {fileID: 1768978093373392434} + m_PrefabAsset: {fileID: 0} +--- !u!33 &1444122194983607567 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7461238108046391290} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &2160506865115608745 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7461238108046391290} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a53ba8919fa78c14caac473c7e7ce7d3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &8867363453312856971 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 7170742296219383225, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + m_PrefabInstance: {fileID: 1768978093373392434} + m_PrefabAsset: {fileID: 0} +--- !u!33 &8116266233139283823 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8867363453312856971} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &6061467578651748175 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8867363453312856971} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a53ba8919fa78c14caac473c7e7ce7d3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab.meta new file mode 100644 index 0000000000..75b0f6a314 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ClientPlayer.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3e5167b6e6bcb5645abb2dbc0078091e +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial new file mode 100644 index 0000000000..5aeefdde15 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!134 &13400000 +PhysicsMaterial: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Floor + serializedVersion: 2 + m_DynamicFriction: 2.5 + m_StaticFriction: 2 + m_Bounciness: 0 + m_FrictionCombine: 3 + m_BounceCombine: 2 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial.meta new file mode 100644 index 0000000000..a55550d94a --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Floor.physicMaterial.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c574f6ade946d94f9ec0183e3bc4579 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 13400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab new file mode 100644 index 0000000000..edcdf3071c --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab @@ -0,0 +1,111 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &8924170145835402666 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8111781018561290000} + - component: {fileID: 5133274882688487605} + - component: {fileID: 6978882906433643647} + - component: {fileID: 894093325933845257} + m_Layer: 0 + m_Name: Ramp_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &8111781018561290000 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + serializedVersion: 2 + m_LocalRotation: {x: 0.000000021855694, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &5133274882688487605 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!23 &6978882906433643647 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!64 &894093325933845257 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8924170145835402666} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 5 + m_Convex: 0 + m_CookingOptions: 30 + m_Mesh: {fileID: -8512782951310809723, guid: 426a2785f8a940049aac2c246661cf09, type: 3} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab.meta new file mode 100644 index 0000000000..14e8009f6b --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Ramp_Prefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7ab2ae375810b5641a36d327b9f022cf +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial new file mode 100644 index 0000000000..f76e8e1f5a --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!134 &13400000 +PhysicsMaterial: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: RotatingBody + serializedVersion: 2 + m_DynamicFriction: 2.5 + m_StaticFriction: 2 + m_Bounciness: 0 + m_FrictionCombine: 3 + m_BounceCombine: 2 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial.meta new file mode 100644 index 0000000000..4650ebf5b0 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.physicMaterial.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c16e8d98094923449892b28a230ddb9c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 13400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab new file mode 100644 index 0000000000..73c04c02fa --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab @@ -0,0 +1,1138 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &755183729697733696 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 522841341294848418} + - component: {fileID: 6376943934387906635} + - component: {fileID: 4793038284696412839} + m_Layer: 0 + m_Name: WallPusherA_Inner + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &522841341294848418 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 755183729697733696} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 4.05, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &6376943934387906635 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 755183729697733696} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &4793038284696412839 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 755183729697733696} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 1 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} +--- !u!1 &1070469363212057228 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4967607898511807569} + - component: {fileID: 4081511419635538304} + - component: {fileID: 3264090684553753291} + - component: {fileID: 2074962368930179783} + - component: {fileID: 2659845886479429441} + - component: {fileID: 365707591732078506} + m_Layer: 0 + m_Name: Tunnel_Prefab (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &4967607898511807569 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + serializedVersion: 2 + m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 5, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} +--- !u!33 &4081511419635538304 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Mesh: {fileID: 5060444177187149915, guid: 63fcabcd345d556498e09f748683088e, type: 3} +--- !u!23 &3264090684553753291 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &2074962368930179783 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Material: {fileID: 13400000, guid: c16e8d98094923449892b28a230ddb9c, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2.502674, y: 6.0000024, z: 0.25225586} + m_Center: {x: 0, y: -5.4553292e-15, z: 2.3838842} +--- !u!65 &2659845886479429441 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.19382703, y: 6.0000024, z: 2.2399507} + m_Center: {x: -1.1314733, y: -1.7786642e-15, z: 1.1251621} +--- !u!65 &365707591732078506 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1070469363212057228} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.16720939, y: 6.0000024, z: 2.256102} + m_Center: {x: 1.1123942, y: -1.893427e-15, z: 1.1332378} +--- !u!1 &1227036625448805321 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6179387521291227856} + - component: {fileID: 6043205804484853104} + - component: {fileID: 7583159525709273484} + - component: {fileID: 8095289646395249164} + m_Layer: 0 + m_Name: Ramp_PrefabA + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &6179387521291227856 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + serializedVersion: 2 + m_LocalRotation: {x: 0.000000021855694, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.25, z: 7.993} + m_LocalScale: {x: 8, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3394725634534525932} + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &6043205804484853104 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!23 &7583159525709273484 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!64 &8095289646395249164 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1227036625448805321} + m_Material: {fileID: 13400000, guid: c16e8d98094923449892b28a230ddb9c, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 5 + m_Convex: 1 + m_CookingOptions: 30 + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!1 &1451894099667441545 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3394725634534525932} + - component: {fileID: 2385627259374300527} + m_Layer: 0 + m_Name: BridgeGap + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3394725634534525932 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1451894099667441545} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: 1, z: -0.000000021855694, w: 0} + m_LocalPosition: {x: 0, y: 0.14, z: -1.0100002} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6179387521291227856} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!136 &2385627259374300527 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1451894099667441545} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.1 + m_Height: 1.15 + m_Direction: 0 + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &2406660182425334495 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1877966211327071347} + - component: {fileID: 3334163744623252575} + - component: {fileID: 5776191766534439988} + - component: {fileID: 6535282611511474253} + - component: {fileID: 8963258823839906869} + - component: {fileID: 7304575238635320140} + m_Layer: 0 + m_Name: Tunnel_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &1877966211327071347 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + serializedVersion: 2 + m_LocalRotation: {x: -0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 4.96, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: -90, y: 0, z: 0} +--- !u!33 &3334163744623252575 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Mesh: {fileID: 5060444177187149915, guid: 63fcabcd345d556498e09f748683088e, type: 3} +--- !u!23 &5776191766534439988 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &6535282611511474253 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2.502674, y: 6.0000024, z: 0.258326} + m_Center: {x: 0, y: 5.4121983e-15, z: 2.3808491} +--- !u!65 &8963258823839906869 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2527809, y: 6.0000024, z: 2.2399507} + m_Center: {x: -1.1249466, y: 1.7786642e-15, z: 1.1251621} +--- !u!65 &7304575238635320140 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2406660182425334495} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.26525307, y: 6.0000024, z: 2.256102} + m_Center: {x: 1.1187105, y: 1.893427e-15, z: 1.1332378} +--- !u!1 &2492174073242389836 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6182591213277846585} + - component: {fileID: 3395949877673187758} + m_Layer: 0 + m_Name: BridgeGap (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6182591213277846585 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2492174073242389836} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -1, z: 0.000000021855694, w: 0} + m_LocalPosition: {x: 0, y: 0.14, z: -1.01} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7826839884382864090} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!136 &3395949877673187758 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2492174073242389836} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.1 + m_Height: 1.15 + m_Direction: 0 + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &3038314618705458857 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3570252208687936609} + - component: {fileID: 4156819582579138816} + - component: {fileID: 2806575150631930745} + m_Layer: 0 + m_Name: WallPusherB_Inner + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &3570252208687936609 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3038314618705458857} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -4.07, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &4156819582579138816 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3038314618705458857} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &2806575150631930745 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3038314618705458857} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 0 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} +--- !u!1 &4405129256840456534 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5628673131512617452} + - component: {fileID: 1065561891622964567} + - component: {fileID: 4468158207537748033} + m_Layer: 0 + m_Name: WallPusherA_Outer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &5628673131512617452 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4405129256840456534} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 4.89, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &1065561891622964567 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4405129256840456534} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &4468158207537748033 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4405129256840456534} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 0 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} +--- !u!1 &5415449980466536476 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4291553105548296809} + - component: {fileID: 8134939553748259768} + - component: {fileID: 3307166493715739449} + - component: {fileID: 850162744905636139} + m_Layer: 0 + m_Name: RotatingBody + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4291553105548296809 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1877966211327071347} + - {fileID: 4967607898511807569} + - {fileID: 522841341294848418} + - {fileID: 5628673131512617452} + - {fileID: 3570252208687936609} + - {fileID: 5389614242607036533} + - {fileID: 6179387521291227856} + - {fileID: 7826839884382864090} + - {fileID: 2053800158975384476} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &8134939553748259768 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 8.023605, y: 8.144613, z: 20.106205} + m_Center: {x: -0.03800702, y: 4.1726913, z: -0.02903366} +--- !u!114 &3307166493715739449 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 1921503253 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 2 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 1 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 +--- !u!114 &850162744905636139 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5415449980466536476} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 374ac199eb18f0f468bc018a722775c2, type: 3} + m_Name: + m_EditorClassIdentifier: + AuthorityMode: 1 + TickSyncChildren: 1 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.1 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.1 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 0 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 0 + RotationSpeed: 0.25 + RotateDirection: 0 + ZAxisMove: 0 +--- !u!1 &5463907175177238004 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2053800158975384476} + - component: {fileID: 2688281628964240045} + m_Layer: 0 + m_Name: Point Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2053800158975384476 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5463907175177238004} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 6.54, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!108 &2688281628964240045 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5463907175177238004} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 2 + m_Color: {r: 0.9622642, g: 0.9622642, b: 0.9622642, a: 1} + m_Intensity: 2 + m_Range: 20 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 0 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!1 &6529740436184164063 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7826839884382864090} + - component: {fileID: 2797882924116045225} + - component: {fileID: 1097787484049525257} + - component: {fileID: 570708159460268413} + m_Layer: 0 + m_Name: Ramp_PrefabB + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &7826839884382864090 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 1, z: 0, w: 0} + m_LocalPosition: {x: 0, y: 0.25, z: -7.993} + m_LocalScale: {x: 8, y: 0.5, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6182591213277846585} + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 180, z: 0} +--- !u!33 &2797882924116045225 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!23 &1097787484049525257 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!64 &570708159460268413 +MeshCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6529740436184164063} + m_Material: {fileID: 13400000, guid: c16e8d98094923449892b28a230ddb9c, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 5 + m_Convex: 1 + m_CookingOptions: 30 + m_Mesh: {fileID: -6265776187016570482, guid: 4c31b0c9eb3dcdf4890cd904bf277cdf, type: 3} +--- !u!1 &8002386465640125644 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5389614242607036533} + - component: {fileID: 4101224782146625552} + - component: {fileID: 6895917012766152111} + m_Layer: 0 + m_Name: WallPusherB_Outer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &5389614242607036533 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8002386465640125644} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -4.83, y: 2.27, z: 0} + m_LocalScale: {x: 4, y: 2, z: 2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4291553105548296809} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &4101224782146625552 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8002386465640125644} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2, y: 2.25, z: 6} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &6895917012766152111 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8002386465640125644} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2c40721ca0fd31645a742e5ad0e0cdc5, type: 3} + m_Name: + m_EditorClassIdentifier: + PushDirection: 1 + ToCenterDirOrig: {x: 0, y: 0, z: 0} + ToCenterDirCalc: {x: 0, y: 0, z: 0} + ToCenterDirTrans: {x: 0, y: 0, z: 0} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab.meta new file mode 100644 index 0000000000..d22d47036e --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/RotatingBody.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0123d7125346c274da00b38e950a266b +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab new file mode 100644 index 0000000000..f636c60a54 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab @@ -0,0 +1,794 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &910007655143077103 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6283120762215196916} + - component: {fileID: 3739510624437302406} + m_Layer: 0 + m_Name: CornerBumper (1) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6283120762215196916 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007655143077103} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0.9244967, z: -0, w: -0.38119} + m_LocalPosition: {x: -29.72, y: 0.98, z: 29.82} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -224.815, z: 0} +--- !u!65 &3739510624437302406 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 910007655143077103} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &1854705290947220173 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2290144462706082272} + - component: {fileID: 4559046433245738380} + m_Layer: 0 + m_Name: CornerBumper (2) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2290144462706082272 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705290947220173} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0.3771283, z: -0, w: -0.92616105} + m_LocalPosition: {x: -29.53, y: 0.98, z: -29.71} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -315.688, z: 0} +--- !u!65 &4559046433245738380 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854705290947220173} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &4012615691354089848 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691354089844} + - component: {fileID: 4012615691354089845} + - component: {fileID: 4012615691354089850} + - component: {fileID: 4012615691354089851} + m_Layer: 0 + m_Name: Floor + m_TagString: Floor + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615691354089844 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: -0.50000006, z: 0} + m_LocalScale: {x: 60, y: 1, z: 60} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691354089845 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691354089850 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 42c4a0ad1f9d67a45b12f68697321aad, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615691354089851 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691354089848} + m_Material: {fileID: 13400000, guid: 2c574f6ade946d94f9ec0183e3bc4579, type: 2} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &4012615691503252843 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691503252839} + - component: {fileID: 4012615691503252836} + - component: {fileID: 4012615691503252837} + - component: {fileID: 4012615691503252842} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615691503252839 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691503252836 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691503252837 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615691503252842 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691503252843} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: -0.5, y: 0, z: 0} +--- !u!1 &4012615691965054905 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615691965054901} + - component: {fileID: 4012615691965054906} + - component: {fileID: 4012615691965054907} + - component: {fileID: 4012615691965054904} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615691965054901 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: 30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615691965054906 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615691965054907 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615691965054904 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615691965054905} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: 0.5} +--- !u!1 &4012615692269653858 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692269653854} + - component: {fileID: 4012615692269653855} + - component: {fileID: 4012615692269653852} + - component: {fileID: 4012615692269653853} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615692269653854 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0.49999994, z: -30.5} + m_LocalScale: {x: 60, y: 3, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615692269653855 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615692269653852 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615692269653853 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692269653858} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 2} + m_Center: {x: 0, y: 0, z: -0.5} +--- !u!1 &4012615692778511854 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692778511849} + m_Layer: 0 + m_Name: SceneLevelGeometry + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615692778511849 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692778511854} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.000000059604645, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4012615691354089844} + - {fileID: 4012615691965054901} + - {fileID: 4012615692269653854} + - {fileID: 4012615691503252839} + - {fileID: 4012615692791378778} + - {fileID: 3910294717376836327} + - {fileID: 6283120762215196916} + - {fileID: 2290144462706082272} + - {fileID: 6959258897999621209} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &4012615692791378782 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012615692791378778} + - component: {fileID: 4012615692791378779} + - component: {fileID: 4012615692791378776} + - component: {fileID: 4012615692791378777} + m_Layer: 0 + m_Name: Side + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4012615692791378778 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 30.5, y: 0.49999994, z: 0} + m_LocalScale: {x: 1, y: 3, z: 62} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &4012615692791378779 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &4012615692791378776 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 9c73b921ea39f4344a19c2d1c7d6b314, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4012615692791378777 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4012615692791378782} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2, y: 1, z: 1} + m_Center: {x: 0.5, y: 0, z: 0} +--- !u!1 &4674276234353933548 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3910294717376836327} + - component: {fileID: 3136259738973340924} + m_Layer: 0 + m_Name: CornerBumper + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3910294717376836327 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276234353933548} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: -0.38268343, z: 0, w: 0.92387956} + m_LocalPosition: {x: 29.7, y: 0.98, z: -29.61} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -45, z: 0} +--- !u!65 &3136259738973340924 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4674276234353933548} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &7080625901286762351 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6959258897999621209} + - component: {fileID: 7672408768716900064} + m_Layer: 0 + m_Name: CornerBumper (3) + m_TagString: Boundary + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6959258897999621209 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625901286762351} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: 0.93588465, z: -0, w: -0.35230666} + m_LocalPosition: {x: 29.26, y: 0.98, z: 29.45} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4012615692778511849} + m_LocalEulerAnglesHint: {x: 0, y: -498.74298, z: 0} +--- !u!65 &7672408768716900064 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7080625901286762351} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 4, y: 2, z: 1} + m_Center: {x: 0, y: 0, z: 0} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab.meta new file mode 100644 index 0000000000..154fd718e7 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/SceneLevelGeometry.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3ec484313a7a6754dac871e620df8db2 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab new file mode 100644 index 0000000000..8e6ca8383c --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab @@ -0,0 +1,656 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &324680519892629439 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7909757205025274661} + - component: {fileID: 4497278534226153218} + m_Layer: 0 + m_Name: PlayerBallChild1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7909757205025274661 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 324680519892629439} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 1, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8978127728034846793} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4497278534226153218 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 324680519892629439} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkTransformExpanded: 0 + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 1 + ExpandNetworkTransform: 1 + RotationAxis: 2 + RotationSpeed: 1.5 +--- !u!1 &1574061277261592297 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2851817463696415024} + - component: {fileID: 8353621747658706578} + m_Layer: 0 + m_Name: PlayerBallChild2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2851817463696415024 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1574061277261592297} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: -1, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8978127728034846793} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8353621747658706578 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1574061277261592297} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkTransformExpanded: 0 + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 0 + ExpandNetworkTransform: 1 + RotationAxis: 0 + RotationSpeed: 1.5 +--- !u!1 &3152047010790481352 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2530875210329125868} + - component: {fileID: 7166766943079311595} + - component: {fileID: 2218667139823191844} + m_Layer: 0 + m_Name: ParentedText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2530875210329125868 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3152047010790481352} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1.91, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7209693128479548322} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &7166766943079311595 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3152047010790481352} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!102 &2218667139823191844 +TextMesh: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3152047010790481352} + m_Text: "Hello \nWorld" + m_OffsetZ: 0 + m_CharacterSize: 0.15 + m_LineSpacing: 1 + m_Anchor: 4 + m_Alignment: 1 + m_TabSize: 4 + m_FontSize: 20 + m_FontStyle: 1 + m_RichText: 1 + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_Color: + serializedVersion: 2 + rgba: 4294967295 +--- !u!1 &3385537716926926172 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6528377011520223688} + - component: {fileID: 7760305833775493645} + m_Layer: 0 + m_Name: Direction + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6528377011520223688 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3385537716926926172} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.529, z: 0.36} + m_LocalScale: {x: 0.5, y: 0.35, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7209693128479548322} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &7760305833775493645 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3385537716926926172} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 0 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!1 &4710599683293591777 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7209693128479548322} + - component: {fileID: 2033351919148783259} + - component: {fileID: 1689822717476917681} + - component: {fileID: 6582615468703263770} + - component: {fileID: 996823777406759075} + - component: {fileID: 4107659158848356731} + - component: {fileID: 8869001495323625783} + - component: {fileID: 769922645502876198} + m_Layer: 0 + m_Name: ServerPlayer + m_TagString: Player + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7209693128479548322 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6528377011520223688} + - {fileID: 2530875210329125868} + - {fileID: 8978127728034846793} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!136 &2033351919148783259 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Height: 2 + m_Direction: 1 + m_Center: {x: 0, y: 0, z: 0} +--- !u!143 &1689822717476917681 +CharacterController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Height: 2 + m_Radius: 0.5 + m_SlopeLimit: 45 + m_StepOffset: 0.3 + m_SkinWidth: 0.08 + m_MinMoveDistance: 0.001 + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &6582615468703263770 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2883015141 + InScenePlacedSourceGlobalObjectIdHash: 2508530451 + DeferredDespawnTick: 0 + Ownership: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 0 + AllowOwnerToParent: 1 +--- !u!114 &996823777406759075 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8e5128237997be649af0cc87dd0eb563, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + ApplyColorToChildren: 1 + IgnoreChildren: + - {fileID: 3385537716926926172} +--- !u!114 &4107659158848356731 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5ce25b0b3f15e6446a88a85787c2f94a, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkTransformExpanded: 0 + AuthorityMode: 1 + TickSyncChildren: 1 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.01 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.1 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 0 + SwitchTransformSpaceWhenParented: 1 + Interpolate: 1 + SlerpPosition: 0 + MoverScriptNoRigidbodyExpanded: 0 + SpawnRadius: 10 + Increment: 1 + RotationSpeed: 1.26 + MovementSpeed: 15 + JumpSpeed: 10 + AirSpeedFactor: 0.35 + Gravity: -9.8 + ContinualChildMotion: 1 +--- !u!65 &8869001495323625783 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1.4, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &769922645502876198 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4710599683293591777} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bb9a9987a689bbe43a05463cf3713b22, type: 3} + m_Name: + m_EditorClassIdentifier: + ServerOnly: 1 + UniqueLocalInstanceContent: ServerPlayer specific local content +--- !u!1 &7170742296219383225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5575000075487772256} + - component: {fileID: 974108613365902635} + m_Layer: 0 + m_Name: PlayerBallChild3 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5575000075487772256 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7170742296219383225} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 1} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8978127728034846793} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &974108613365902635 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7170742296219383225} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkTransformExpanded: 0 + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 1 + ExpandNetworkTransform: 1 + RotationAxis: 1 + RotationSpeed: 1.5 +--- !u!1 &9153294333356437960 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8978127728034846793} + - component: {fileID: 9124254747570484083} + m_Layer: 0 + m_Name: PlayerBallPrime + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8978127728034846793 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9153294333356437960} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.25, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7909757205025274661} + - {fileID: 2851817463696415024} + - {fileID: 5575000075487772256} + m_Father: {fileID: 7209693128479548322} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &9124254747570484083 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9153294333356437960} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 792d7ce524eb358469373fe12babef88, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkTransformExpanded: 0 + AuthorityMode: 1 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 + SyncPositionX: 1 + SyncPositionY: 1 + SyncPositionZ: 1 + SyncRotAngleX: 1 + SyncRotAngleY: 1 + SyncRotAngleZ: 1 + SyncScaleX: 1 + SyncScaleY: 1 + SyncScaleZ: 1 + PositionThreshold: 0.001 + RotAngleThreshold: 0.01 + ScaleThreshold: 0.01 + UseQuaternionSynchronization: 1 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 + InLocalSpace: 1 + SwitchTransformSpaceWhenParented: 0 + Interpolate: 1 + SlerpPosition: 1 + ExpandPlayerBallMotion: 1 + ExpandNetworkTransform: 1 + RotationAxis: 0 + RotationSpeed: 1.5 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab.meta new file mode 100644 index 0000000000..c28eb79b02 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/ServerPlayer.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 90bfa3cd2cce8f14ead59b4dbdae92bb +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab new file mode 100644 index 0000000000..c596d7333d --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab @@ -0,0 +1,154 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1604908963751126680 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2093637950428003362} + - component: {fileID: 3071950872142852999} + - component: {fileID: 956227959320364877} + - component: {fileID: 4985949235297978144} + - component: {fileID: 8456313914433245678} + - component: {fileID: 1138964657491743937} + m_Layer: 0 + m_Name: Tunnel_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &2093637950428003362 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + serializedVersion: 2 + m_LocalRotation: {x: -0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: -90.00001, y: 0, z: 0} +--- !u!33 &3071950872142852999 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Mesh: {fileID: 5060444177187149915, guid: 63fcabcd345d556498e09f748683088e, type: 3} +--- !u!23 &956227959320364877 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &4985949235297978144 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 2.502674, y: 6.0000024, z: 0.258326} + m_Center: {x: 0, y: 5.4121983e-15, z: 2.3808491} +--- !u!65 &8456313914433245678 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.2527809, y: 6.0000024, z: 2.2399507} + m_Center: {x: -1.1249466, y: 1.7786642e-15, z: 1.1251621} +--- !u!65 &1138964657491743937 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604908963751126680} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.26525307, y: 6.0000024, z: 2.256102} + m_Center: {x: 1.1187105, y: 1.893427e-15, z: 1.1332378} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab.meta new file mode 100644 index 0000000000..58def4a902 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Tunnel_Prefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4b50ff3d475fc3f4fa77ac6aa6e679f2 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab new file mode 100644 index 0000000000..f8dde4effc --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab @@ -0,0 +1,132 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &7993119983977949264 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7324705577624711914} + - component: {fileID: 5911343394670230863} + - component: {fileID: 8497650616581704069} + - component: {fileID: 9105854698657379725} + - component: {fileID: 1277700310800588604} + m_Layer: 0 + m_Name: Wall_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 73 + m_IsActive: 1 +--- !u!4 &7324705577624711914 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + serializedVersion: 2 + m_LocalRotation: {x: 0.000000021855694, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &5911343394670230863 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Mesh: {fileID: 6352809919239313146, guid: 34560503bf9d61046b252db98a8cf770, type: 3} +--- !u!23 &8497650616581704069 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 2e2c886f4af8e304eb9a1e2e50d023b3, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 2 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &9105854698657379725 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.25, y: 1.0095696, z: 2.9958286} + m_Center: {x: 0, y: 0.5047848, z: 1.5020857} +--- !u!65 &1277700310800588604 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7993119983977949264} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.25, y: 2.0055175, z: 3.000146} + m_Center: {x: 0, y: 1.0027587, z: -1.499927} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab.meta b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab.meta new file mode 100644 index 0000000000..42ef5284f2 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Prefabs/Wall_Prefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3b9516be83427084ca3fffca42e7b6da +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Resources.meta b/Examples/OverridingScenesAndPrefabs/Assets/Resources.meta new file mode 100644 index 0000000000..edebf21a13 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6a51a9fbd254e544eb3e85853865f80d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json b/Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json new file mode 100644 index 0000000000..6f4bfb7103 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json @@ -0,0 +1 @@ +{"androidStore":"GooglePlay"} \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json.meta b/Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json.meta new file mode 100644 index 0000000000..557e7d707c --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Resources/BillingMode.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a1890189503409a4bb24dd4f0eab1f0a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes.meta new file mode 100644 index 0000000000..b398c5b4ab --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9bb955f9d9ef9c34d897f353c8643a1d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity new file mode 100644 index 0000000000..1493ee48f9 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity @@ -0,0 +1,282 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &455857869 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 455857870} + - component: {fileID: 455857873} + - component: {fileID: 455857874} + - component: {fileID: 455857872} + - component: {fileID: 455857871} + m_Layer: 0 + m_Name: NetworkManagerBootstrapper + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &455857870 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &455857871 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProtocolType: 0 + m_UseWebSockets: 0 + m_UseEncryption: 0 + m_MaxPacketQueueSize: 128 + m_MaxPayloadSize: 6144 + m_HeartbeatTimeoutMS: 500 + m_ConnectTimeoutMS: 1000 + m_MaxConnectAttempts: 60 + m_DisconnectTimeoutMS: 30000 + ConnectionData: + Address: 127.0.0.1 + Port: 7777 + ServerListenAddress: 127.0.0.1 + DebugSimulator: + PacketDelayMS: 0 + PacketJitterMS: 0 + PacketDropRate: 0 +--- !u!114 &455857872 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7db19e0e150e50d4ab23c69a086b8b6e, type: 3} + m_Name: + m_EditorClassIdentifier: + NetworkManagerExpanded: 1 + NetworkConfig: + ProtocolVersion: 0 + NetworkTransport: {fileID: 455857871} + PlayerPrefab: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + Prefabs: + NetworkPrefabsLists: + - {fileID: 11400000, guid: 3d25a2b1f6c12ee47bf7601c2edd7e70, type: 2} + TickRate: 30 + ClientConnectionBufferTimeout: 10 + ConnectionApproval: 0 + ConnectionData: + EnableTimeResync: 0 + TimeResyncInterval: 30 + EnsureNetworkVariableLengthSafety: 0 + EnableSceneManagement: 1 + ForceSamePrefabs: 1 + RecycleNetworkIds: 1 + NetworkIdRecycleDelay: 120 + RpcHashSize: 0 + LoadSceneTimeOut: 120 + SpawnTimeout: 10 + EnableNetworkLogs: 1 + NetworkTopology: 0 + UseCMBService: 0 + AutoSpawnPlayerPrefabClientSide: 1 + NetworkProfilingMetrics: 1 + OldPrefabList: [] + RunInBackground: 1 + LogLevel: 1 + NetworkManagerBootstrapperExpanded: 1 + TargetFrameRate: 100 + EnableVSync: 0 + m_OriginalVSyncCount: 1 + m_ServicesRegistered: 1 +--- !u!114 &455857873 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c4251d3d650053a419a5c503babb13a6, type: 3} + m_Name: + m_EditorClassIdentifier: + SceneBootstrapLoaderExpanded: 0 + MainMenuSceneAsset: {fileID: 102900000, guid: 1ec6afcedd7df124e91af7bfbcea59b2, type: 3} + ServerSceneDefines: + DefaultActiveScene: SharedScene + LocalScenes: + - ServerScene + DefaultActiveSceneAsset: {fileID: 102900000, guid: 9fc0d4010bbf28b4594072e72b8655ab, type: 3} + LocalSceneAssets: + - {fileID: 102900000, guid: ee7bfa213000a594bb8011364915602e, type: 3} + SharedScenes: [] + SharedSceneAssets: [] + ClientSceneDefines: + DefaultActiveScene: SharedScene + LocalScenes: + - ClientScene + DefaultActiveSceneAsset: {fileID: 102900000, guid: 9fc0d4010bbf28b4594072e72b8655ab, type: 3} + LocalSceneAssets: + - {fileID: 102900000, guid: d8ac305138d51a84dbbb734b81c4054e, type: 3} + m_MainMenuScene: MainMenu +--- !u!114 &455857874 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 455857869} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5c472ff64b067344893ed2e632d0f9f1, type: 3} + m_Name: + m_EditorClassIdentifier: + NetworkPrefab: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + NetworkPrefabOverride: {fileID: 6472733969592893139, guid: 3e5167b6e6bcb5645abb2dbc0078091e, type: 3} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 455857870} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity.meta new file mode 100644 index 0000000000..e2c316c516 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/BootstrapScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8077653236d44fd4b92cd4a96193e8b6 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset new file mode 100644 index 0000000000..b8bbae0a72 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset @@ -0,0 +1,195 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!181963792 &2655988077585873504 +Preset: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Camera + m_TargetType: + m_NativeTypeID: 20 + m_ManagedTypePPtr: {fileID: 0} + m_ManagedTypeFallback: + m_Properties: + - target: {fileID: 0} + propertyPath: m_Enabled + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_ClearFlags + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.r + value: 0.066037714 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.g + value: 0.066037714 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.b + value: 0.066037714 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BackGroundColor.a + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_projectionMatrixMode + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_GateFitMode + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_FOVAxisMode + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Iso + value: 200 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_ShutterSpeed + value: 0.005 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Aperture + value: 16 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_FocusDistance + value: 10 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_FocalLength + value: 50 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BladeCount + value: 5 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Curvature.x + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Curvature.y + value: 11 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_BarrelClipping + value: 0.25 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Anamorphism + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_SensorSize.x + value: 36 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_SensorSize.y + value: 24 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_LensShift.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_LensShift.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.width + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_NormalizedViewPortRect.height + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: near clip plane + value: 0.3 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: far clip plane + value: 1000 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: field of view + value: 60 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: orthographic + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: orthographic size + value: 5 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_Depth + value: -1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_CullingMask.m_Bits + value: 4294967295 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_RenderingPath + value: -1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_TargetTexture + value: + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_TargetDisplay + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_TargetEye + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_HDR + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_AllowMSAA + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_AllowDynamicResolution + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_ForceIntoRT + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_OcclusionCulling + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_StereoConvergence + value: 10 + objectReference: {fileID: 0} + - target: {fileID: 0} + propertyPath: m_StereoSeparation + value: 0.022 + objectReference: {fileID: 0} + m_ExcludedProperties: [] diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset.meta new file mode 100644 index 0000000000..3e327ecf36 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/Camera.preset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1807b530602915743868e6c3bdc1a93c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2655988077585873504 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity new file mode 100644 index 0000000000..06eaad444d --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity @@ -0,0 +1,382 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1001 &27008424 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalScale.x + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalScale.z + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.y + value: -0.25 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511854, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_Name + value: SceneLevelGeometry (1) + objectReference: {fileID: 0} + m_RemovedComponents: + - {fileID: 4012615691354089851, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615692791378777, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615691503252842, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615692269653853, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615691965054904, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + m_RemovedGameObjects: + - {fileID: 7080625901286762351, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 1854705290947220173, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 910007655143077103, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4674276234353933548, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} +--- !u!1 &638077906 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 638077907} + - component: {fileID: 638077911} + - component: {fileID: 638077910} + m_Layer: 5 + m_Name: ClientContent + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &638077907 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 638077906} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1082114250} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: -32} + m_SizeDelta: {x: 180, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &638077910 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 638077906} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8396226, g: 0.48815125, b: 0.019802418, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 23 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Client Canvas Content +--- !u!222 &638077911 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 638077906} + m_CullTransparentMesh: 1 +--- !u!1 &1082114246 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1082114250} + - component: {fileID: 1082114249} + - component: {fileID: 1082114248} + - component: {fileID: 1082114247} + m_Layer: 5 + m_Name: ClientCanvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1082114247 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1082114246} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1082114248 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1082114246} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1082114249 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1082114246} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1082114250 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1082114246} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 638077907} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1082114250} + - {fileID: 27008424} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity.meta new file mode 100644 index 0000000000..65393dbc64 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ClientScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d8ac305138d51a84dbbb734b81c4054e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity new file mode 100644 index 0000000000..45e6ae776b --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity @@ -0,0 +1,607 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &559666861 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 559666865} + - component: {fileID: 559666864} + - component: {fileID: 559666863} + - component: {fileID: 559666862} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &559666862 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 559666861} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &559666863 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 559666861} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &559666864 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 559666861} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &559666865 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 559666861} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1419645487} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1031380542 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1031380545} + - component: {fileID: 1031380544} + - component: {fileID: 1031380543} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1031380543 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1031380542} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1031380544 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1031380542} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1031380545 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1031380542} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1273064315 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1273064318} + - component: {fileID: 1273064317} + - component: {fileID: 1273064316} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1273064316 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1273064315} + m_Enabled: 1 +--- !u!20 &1273064317 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1273064315} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.16037738, g: 0.16037738, b: 0.16037738, a: 1} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1273064318 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1273064315} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1346095180 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1346095182} + - component: {fileID: 1346095181} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1346095181 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1346095180} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!4 &1346095182 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1346095180} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &1419645486 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1419645487} + - component: {fileID: 1419645491} + - component: {fileID: 1419645490} + - component: {fileID: 1419645489} + - component: {fileID: 1419645488} + m_Layer: 5 + m_Name: MainMenu + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1419645487 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1419645486} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 559666865} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 180, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1419645488 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1419645486} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6637cd674efb56a48a3d4d545d23a8d3, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 +--- !u!114 &1419645489 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1419645486} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 622675322 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!114 &1419645490 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1419645486} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8396226, g: 0.48815125, b: 0.019802418, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 23 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Mock Main Menu ' +--- !u!222 &1419645491 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1419645486} + m_CullTransparentMesh: 1 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1273064318} + - {fileID: 1346095182} + - {fileID: 559666865} + - {fileID: 1031380545} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity.meta new file mode 100644 index 0000000000..f89877b055 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/MainMenu.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1ec6afcedd7df124e91af7bfbcea59b2 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset new file mode 100644 index 0000000000..afdf3f97be --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset @@ -0,0 +1,26 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e651dbb3fbac04af2b8f5abf007ddc23, type: 3} + m_Name: ProjectPrefabs + m_EditorClassIdentifier: + IsDefault: 0 + List: + - Override: 0 + Prefab: {fileID: 4710599683293591777, guid: 90bfa3cd2cce8f14ead59b4dbdae92bb, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + - Override: 0 + Prefab: {fileID: 6472733969592893139, guid: 3e5167b6e6bcb5645abb2dbc0078091e, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset.meta new file mode 100644 index 0000000000..402e4425d7 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ProjectPrefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3d25a2b1f6c12ee47bf7601c2edd7e70 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity new file mode 100644 index 0000000000..577973e816 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity @@ -0,0 +1,402 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &405623521 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 405623522} + - component: {fileID: 405623526} + - component: {fileID: 405623525} + m_Layer: 5 + m_Name: ConnectedPlayers + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &405623522 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 405623521} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1605560297} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: -116, y: 27} + m_SizeDelta: {x: 180, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &405623525 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 405623521} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8396226, g: 0.48815125, b: 0.019802418, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Players: 0' +--- !u!222 &405623526 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 405623521} + m_CullTransparentMesh: 1 +--- !u!1 &1516152272 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1516152273} + - component: {fileID: 1516152277} + - component: {fileID: 1516152276} + m_Layer: 5 + m_Name: ServerRuntime + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1516152273 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1516152272} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1605560297} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: -116, y: 61} + m_SizeDelta: {x: 180, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1516152276 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1516152272} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8396226, g: 0.48815125, b: 0.019802418, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'NetworkTick: 0' +--- !u!222 &1516152277 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1516152272} + m_CullTransparentMesh: 1 +--- !u!1 &1605560293 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1605560297} + - component: {fileID: 1605560296} + - component: {fileID: 1605560295} + - component: {fileID: 1605560294} + - component: {fileID: 1605560298} + m_Layer: 5 + m_Name: ServerCanvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1605560294 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1605560293} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1605560295 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1605560293} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1605560296 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1605560293} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1605560297 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1605560293} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1516152273} + - {fileID: 405623522} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!114 &1605560298 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1605560293} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d370147e4c421014cb5dd4eee3b6a373, type: 3} + m_Name: + m_EditorClassIdentifier: + ServerTime: {fileID: 1516152276} + PlayerCount: {fileID: 405623525} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1605560297} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity.meta new file mode 100644 index 0000000000..21ce0ed652 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/ServerScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ee7bfa213000a594bb8011364915602e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity new file mode 100644 index 0000000000..1af0fa5fa5 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity @@ -0,0 +1,1343 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &28232985 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 28232986} + - component: {fileID: 28232988} + - component: {fileID: 28232987} + - component: {fileID: 28232989} + - component: {fileID: 28232990} + m_Layer: 5 + m_Name: ServerHostClientDisplay + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &28232986 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 479361665} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: 0, y: 20.5} + m_SizeDelta: {x: 180, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &28232987 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 23 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: +--- !u!222 &28232988 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_CullTransparentMesh: 1 +--- !u!114 &28232989 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 536662705 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 0 + SpawnWithObservers: 1 + DontDestroyWithOwner: 1 + AutoObjectParentSync: 0 + SyncOwnerTransformWhenParented: 0 + AllowOwnerToParent: 0 +--- !u!114 &28232990 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 28232985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6637cd674efb56a48a3d4d545d23a8d3, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 +--- !u!1001 &45185844 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: OnExitTransferParentOnStay + value: + objectReference: {fileID: 621748559} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: DontDeparentIfParentedByOtherBody + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 3246499739 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: -60 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: -49.1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: StationaryBody-B + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Size.z + value: 19.891119 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.x + value: -0.038006783 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.z + value: -0.13657665 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1001 &66674670 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: OnExitTransferParentOnStay + value: + objectReference: {fileID: 520394643} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: DontDeparentIfParentedByOtherBody + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 4013775021 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: -60 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: -33.32 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: StationaryBody-A + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Size.z + value: 19.906477 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.x + value: -0.038006783 + objectReference: {fileID: 0} + - target: {fileID: 8134939553748259768, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Center.z + value: 0.070830345 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1 &479361661 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 479361665} + - component: {fileID: 479361664} + - component: {fileID: 479361663} + - component: {fileID: 479361662} + m_Layer: 5 + m_Name: SharedCanvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &479361662 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &479361663 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &479361664 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &479361665 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 479361661} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 28232986} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!114 &520394643 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + m_PrefabInstance: {fileID: 45185844} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 374ac199eb18f0f468bc018a722775c2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &621748559 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + m_PrefabInstance: {fileID: 66674670} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 374ac199eb18f0f468bc018a722775c2, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &737227285 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 737227286} + m_Layer: 0 + m_Name: PointF + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &737227286 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 737227285} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 60.5, y: -0.34, z: -68.5} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &748186899 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalScale.x + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalScale.z + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.y + value: -0.25 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511849, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4012615692778511854, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + propertyPath: m_Name + value: SceneLevelGeometry + objectReference: {fileID: 0} + m_RemovedComponents: + - {fileID: 4012615691354089850, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615691354089845, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615692791378779, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615691503252836, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615692269653855, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615691965054906, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615692791378776, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615691503252837, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615692269653852, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + - {fileID: 4012615691965054907, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 3ec484313a7a6754dac871e620df8db2, type: 3} +--- !u!1001 &857399335 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0.25 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotateDirection + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 1449196534 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: -60.7 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: 56.8 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: RotatingBody + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0, g: 0, b: 0, a: 1} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + serializedVersion: 2 + m_LocalRotation: {x: 0.15212336, y: 0, z: 0, w: 0.98836154} + m_LocalPosition: {x: 0, y: 4.5, z: -5.5} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 17.5, y: 0, z: 0} +--- !u!1 &1184702125 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1184702128} + - component: {fileID: 1184702127} + - component: {fileID: 1184702126} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1184702126 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184702125} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1184702127 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184702125} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1184702128 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184702125} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1364678139 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1364678140} + m_Layer: 0 + m_Name: PointC + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1364678140 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1364678139} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: 40.6, z: -70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1525255853 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1525255854} + m_Layer: 0 + m_Name: PointD + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1525255854 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1525255853} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: -0.34, z: -70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &1591298748 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: ZAxisMove + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: PathMovement.Array.size + value: 4 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: NetworkTransformExpanded + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotatingBodyLogicExpanded + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[0]' + value: + objectReference: {fileID: 1851254366} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[1]' + value: + objectReference: {fileID: 1939125342} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[2]' + value: + objectReference: {fileID: 1364678139} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[3]' + value: + objectReference: {fileID: 1525255853} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 4258258070 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: 10.9 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: 70 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: 'ElevatorBody ' + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1 &1851254366 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1851254367} + m_Layer: 0 + m_Name: PointA + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1851254367 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1851254366} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: -0.34, z: 70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1900905072 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1900905073} + m_Layer: 0 + m_Name: PointE + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1900905073 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1900905072} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 60.5, y: -0.34, z: 70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1939125342 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1939125343} + m_Layer: 0 + m_Name: PointB + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1939125343 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1939125342} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10.9, y: 40.6, z: 70} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &1968567121 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: ZAxisMove + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotationSpeed + value: 0.3 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: PathMovement.Array.size + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: RotatingBodyLogicExpanded + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[0]' + value: + objectReference: {fileID: 1900905072} + - target: {fileID: 850162744905636139, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: 'PathMovement.Array.data[1]' + value: + objectReference: {fileID: 737227285} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: GlobalObjectIdHash + value: 1259759422 + objectReference: {fileID: 0} + - target: {fileID: 3307166493715739449, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: InScenePlacedSourceGlobalObjectIdHash + value: 1084435762 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.x + value: 60 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.y + value: -0.34 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalPosition.z + value: 70 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4291553105548296809, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5415449980466536476, guid: 0123d7125346c274da00b38e950a266b, type: 3} + propertyPath: m_Name + value: MovingRotatingBody + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 0123d7125346c274da00b38e950a266b, type: 3} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 963194228} + - {fileID: 705507995} + - {fileID: 479361665} + - {fileID: 1184702128} + - {fileID: 748186899} + - {fileID: 857399335} + - {fileID: 66674670} + - {fileID: 45185844} + - {fileID: 1968567121} + - {fileID: 1591298748} + - {fileID: 1851254367} + - {fileID: 1939125343} + - {fileID: 1364678140} + - {fileID: 1525255854} + - {fileID: 1900905073} + - {fileID: 737227286} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity.meta new file mode 100644 index 0000000000..952bd1e9e1 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scenes/SharedScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9fc0d4010bbf28b4594072e72b8655ab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts.meta new file mode 100644 index 0000000000..528b6db2ae --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f0135d923712c4438b2facb3ce21fb6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs new file mode 100644 index 0000000000..e8bee77712 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs @@ -0,0 +1,108 @@ +using Unity.Netcode; +using UnityEngine; + + +///

+/// An example of how to get server or client specific behaviors without +/// directly using a but still associating +/// with a . +/// +public class InstanceTypeLocalBehavior : MonoBehaviour, INetworkUpdateSystem +{ + [Tooltip("When enabled, this will run only on a server or host. When disabled, this will only run on the owner of the local client player (including host).")] + public bool ServerOnly; + + [Tooltip("This is the unique message example text displayed when running locally.")] + public string UniqueLocalInstanceContent; + + private MoverScriptNoRigidbody m_MoverScriptNoRigidbody; + private NetworkManager m_NetworkManager; + private float m_NextTimeToLogMessage; + + private void Awake() + { + m_MoverScriptNoRigidbody = GetComponent(); + m_MoverScriptNoRigidbody.NotifySpawnStatusChanged += OnSpawnStatusChanged; + } + + /// + /// Adjust this logic to fit your needs. + /// This example makes the InstanceTypeLocalBehavior only update if: + /// - It is a server (including host) and is marked for ServerOnly + /// - It is a client (including host), is not marked for ServerOnly, and the local client is the owner of MoverScriptNoRigidbody. + /// - It is in distributed authority mode, is not marked for ServerOnly, and the local client has authority of the MoverScriptNoRigidbody. + /// + private bool HasAuthority() + { + if (m_NetworkManager == null) + { + return false; + } + + if (!ServerOnly && m_NetworkManager.DistributedAuthorityMode && m_MoverScriptNoRigidbody.HasAuthority) + { + return true; + } + else + { + if (ServerOnly && m_NetworkManager.IsServer) + { + return true; + } + else if (!ServerOnly && m_MoverScriptNoRigidbody.IsOwner) + { + return true; + } + } + return false; + } + + /// + /// + /// Isolate the spawning status to the and just + /// use actions, ecents, or delegates to notify non-shared behaviors that are + /// only on a server or client version of a network prefab that has a . + /// + /// + private void OnSpawnStatusChanged(bool spawned) + { + if (spawned) + { + m_NetworkManager = m_MoverScriptNoRigidbody.NetworkManager; + if (HasAuthority()) + { + NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.Update); + } + } + else + { + // Whether registered or not, it is easier to just unregister always. + NetworkUpdateLoop.UnregisterAllNetworkUpdates(this); + m_NetworkManager = null; + } + } + + /// + /// Invoked only on the instance(s) that have authority to update. + /// + /// + /// + public void NetworkUpdate(NetworkUpdateStage updateStage) + { + if (updateStage == NetworkUpdateStage.Update) + { + OnUpdate(); + } + } + + + private void OnUpdate() + { + if (m_NextTimeToLogMessage < Time.realtimeSinceStartup) + { + var serverClient = m_MoverScriptNoRigidbody.IsServer ? "Server" : "Client"; + NetworkManagerBootstrapper.Instance.LogMessage($"[{Time.realtimeSinceStartup}][{serverClient}-{m_MoverScriptNoRigidbody.name}] {UniqueLocalInstanceContent}"); + m_NextTimeToLogMessage = Time.realtimeSinceStartup + 5.0f; + } + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs.meta new file mode 100644 index 0000000000..e4a7e879ac --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/InstanceTypeLocalBehavior.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bb9a9987a689bbe43a05463cf3713b22 \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs new file mode 100644 index 0000000000..abc1f5e0fc --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs @@ -0,0 +1,376 @@ +using System; +using Unity.Netcode.Components; +using Unity.Netcode; +using UnityEngine; +using Random = UnityEngine.Random; +using Debug = UnityEngine.Debug; + +#region MoverScriptNoRigidbody Custom Editor +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(MoverScriptNoRigidbody), true)] +[CanEditMultipleObjects] +public class MoverScriptNoRigidbodyEditor : NetworkTransformEditor +{ + private SerializedProperty m_Radius; + private SerializedProperty m_Increment; + private SerializedProperty m_RotateSpeed; + private SerializedProperty m_MovementSpeed; + private SerializedProperty m_AirSpeedFactor; + private SerializedProperty m_Gravity; + private SerializedProperty m_ContinualChildMotion; + + + public override void OnEnable() + { + m_Radius = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.SpawnRadius)); + m_Increment = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.Increment)); + m_RotateSpeed = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.RotationSpeed)); + m_MovementSpeed = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.MovementSpeed)); + m_AirSpeedFactor = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.AirSpeedFactor)); + m_Gravity = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.Gravity)); + m_ContinualChildMotion = serializedObject.FindProperty(nameof(MoverScriptNoRigidbody.ContinualChildMotion)); + + base.OnEnable(); + } + + private void DisplayerMoverScriptNoRigidbodyProperties() + { + EditorGUILayout.PropertyField(m_Radius); + EditorGUILayout.PropertyField(m_Increment); + EditorGUILayout.PropertyField(m_RotateSpeed); + EditorGUILayout.PropertyField(m_MovementSpeed); + EditorGUILayout.PropertyField(m_AirSpeedFactor); + EditorGUILayout.PropertyField(m_Gravity); + EditorGUILayout.PropertyField(m_ContinualChildMotion); + } + + public override void OnInspectorGUI() + { + var moverScriptNoRigidbody = target as MoverScriptNoRigidbody; + void SetExpanded(bool expanded) { moverScriptNoRigidbody.MoverScriptNoRigidbodyExpanded = expanded; }; + DrawFoldOutGroup(moverScriptNoRigidbody.GetType(), DisplayerMoverScriptNoRigidbodyProperties, moverScriptNoRigidbody.MoverScriptNoRigidbodyExpanded, SetExpanded); + base.OnInspectorGUI(); + } +} +#endif +#endregion + +/// +/// The player controller for the player prefab +/// +public class MoverScriptNoRigidbody : NetworkTransform +{ +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool MoverScriptNoRigidbodyExpanded; +#endif + + private static bool s_EnablePlayerParentingText = true; + + [Tooltip("Radius range a player will spawn within.")] + [Range(1.0f, 40.0f)] + public float SpawnRadius = 10.0f; + + [Range(0.001f, 10.0f)] + public float Increment = 1.0f; + + [Tooltip("The rotation speed multiplier.")] + [Range(0.01f, 2.0f)] + public float RotationSpeed = 1.0f; + + [Tooltip("The forward movement speed.")] + [Range(0.01f, 30.0f)] + public float MovementSpeed = 15.0f; + + [Tooltip("The jump launching speed.")] + [Range(1.0f, 20f)] + public float JumpSpeed = 10.0f; + + [Tooltip("Determines how much the player's motion is applied when in the air.")] + [Range(0.01f, 1.0f)] + public float AirSpeedFactor = 0.35f; + + [Range(-20.0f, 20.0f)] + public float Gravity = -9.8f; + + [Tooltip("When enabled, the child spheres will continually move. When disabled, the child spheres will only move when the player moves.")] + public bool ContinualChildMotion = true; + + + private TextMesh m_ParentedText; + private PlayerColor m_PlayerColor; + private float m_JumpDelay; + private Vector3 m_WorldMotion = Vector3.zero; + private Vector3 m_CameraOriginalPosition; + private Quaternion m_CameraOriginalRotation; + private CharacterController m_CharacterController; + private PlayerBallMotion m_PlayerBallMotion; + + public event Action NotifySpawnStatusChanged; + + protected override void Awake() + { + m_ParentedText = GetComponentInChildren(); + m_ParentedText?.gameObject.SetActive(false); + m_PlayerColor = GetComponent(); + m_PlayerBallMotion = GetComponentInChildren(); + base.Awake(); + } + + /// + /// Invoked after being instantiated, we can do other pre-spawn related + /// initilization tasks here. + /// + /// + /// This provides you with a reference to the current + /// since that is not set on the until it is spawned. + /// + /// + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_CharacterController = GetComponent(); + // By default, we always disable the CharacterController and only enable it on the + // owner/authority side. + m_CharacterController.enabled = false; + base.OnNetworkPreSpawn(ref networkManager); + } + + /// + /// We are using post spawn to handle any final spawn initializations. + /// At this point we know all NetworkBehaviours on this instance has + /// been spawned. + /// + protected override void OnNetworkPostSpawn() + { + // Authority of this object sends local notifications to any non-networkbehaviour subscribers + NotifySpawnStatusChanged?.Invoke(true); + + m_CharacterController.enabled = CanCommitToTransform; + if (CanCommitToTransform) + { + m_PlayerBallMotion.SetContinualMotion(ContinualChildMotion); + Random.InitState((int)DateTime.Now.Ticks); + transform.position += new Vector3(Random.Range(-SpawnRadius, SpawnRadius), 1.25f, Random.Range(0, SpawnRadius)); + SetState(transform.position, null, null, false); + if (IsLocalPlayer) + { + NetworkObject.DontDestroyWithOwner = false; + m_CameraOriginalPosition = Camera.main.transform.position; + m_CameraOriginalRotation = Camera.main.transform.rotation; + Camera.main.transform.SetParent(transform, false); + } + } + + if (NetworkObject.IsPlayerObject) + { + gameObject.name = $"Player-{OwnerClientId}"; + } + + m_ParentedText?.gameObject.SetActive(true); + UpdateParentedText(); + base.OnNetworkPostSpawn(); + } + + public override void OnNetworkDespawn() + { + // Notify any client or server specific componant that this instance has despawned. + NotifySpawnStatusChanged?.Invoke(false); + if (IsLocalPlayer) + { + m_CharacterController.enabled = false; + Camera.main.transform.SetParent(null, false); + Camera.main.transform.position = m_CameraOriginalPosition; + Camera.main.transform.rotation = m_CameraOriginalRotation; + } + base.OnNetworkDespawn(); + } + + /// + /// Bypass NetworkTransform's OnNetworkObjectParentChanged + /// + public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) + { + if (parentNetworkObject != null) + { + Debug.Log($"Parented under {parentNetworkObject.name}"); + } + UpdateParentedText(); + base.OnNetworkObjectParentChanged(parentNetworkObject); + } + + /// + /// This method handles both client-server and distributed authority network topologies + /// client-server: If we are not the server, then we need to send an Rpc to the server to handle parenting since the Character controller is disabled on the server for all client CharacterControllers (i.e. won't trigger). + /// distributed authority: If we are the authority, then handle parenting locally. + /// + /// + public void SetParent(NetworkObject parent) + { + if ((!NetworkManager.DistributedAuthorityMode && (IsServer || (NetworkObject.AllowOwnerToParent && IsOwner))) || (NetworkManager.DistributedAuthorityMode && HasAuthority)) + { + if (parent != null) + { + NetworkObject.TrySetParent(parent); + } + else + { + NetworkObject.TryRemoveParent(); + } + } + else if (!NetworkManager.DistributedAuthorityMode && !IsServer) + { + SetParentRpc(new NetworkObjectReference(parent)); + } + } + + [Rpc(SendTo.Server)] + public void SetParentRpc(NetworkObjectReference parentReference, RpcParams rpcParams = default) + { + var parent = (NetworkObject)null; + parentReference.TryGet(out parent, NetworkManager); + if (parent != null) + { + NetworkObject.TrySetParent(parent); + } + else + { + NetworkObject.TryRemoveParent(); + } + } + + + private void Update() + { + if (!IsSpawned || !CanCommitToTransform) + { + return; + } + ApplyInput(); + } + + + private Vector3 m_PushMotion = Vector3.zero; + /// + /// Since has issues with collisions and rotating bodies, + /// we have to simulate the collision using triggers. + /// + /// + /// + /// + /// direction to push away from + public void PushAwayFrom(Vector3 normal) + { + m_PushMotion += normal * MovementSpeed * 0.10f * Time.deltaTime; + } + + /// + /// Handles player input + /// + private void ApplyInput() + { + // Simple rotation: + // Since the forward vector is perpendicular to the right vector of the player, we can just + // apply the +/- value to our forward direction and lerp our right vector towards that direction + // in order to get a reasonably smooth rotation. + var rotation = transform.forward; + m_WorldMotion = Vector3.Lerp(m_WorldMotion, m_CharacterController.isGrounded ? Vector3.zero : Vector3.up * Gravity, Time.deltaTime * 2f); + var motion = m_WorldMotion * Time.deltaTime + m_PushMotion; + var moveMotion = 0.0f; + + if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) + { + motion += transform.forward * MovementSpeed * Time.deltaTime * (m_CharacterController.isGrounded ? 1.0f : AirSpeedFactor); + moveMotion = 1.0f; + m_CharacterController.Move(motion); + } + if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) + { + motion += (transform.forward * -MovementSpeed) * Time.deltaTime * (m_CharacterController.isGrounded ? 1.0f : AirSpeedFactor); + moveMotion = -1.0f; + m_CharacterController.Move(motion); + } + + if (!m_CharacterController.isGrounded || m_JumpDelay > Time.realtimeSinceStartup || m_PushMotion.magnitude > 0.01f) + { + m_CharacterController.Move(motion); + } + + if (Input.GetKeyDown(KeyCode.Space) && m_CharacterController.isGrounded) + { + m_JumpDelay = Time.realtimeSinceStartup + 0.5f; + m_WorldMotion = motion + Vector3.up * JumpSpeed; + } + + if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) + { + transform.right = Vector3.Lerp(transform.right, rotation * RotationSpeed, Time.deltaTime).normalized; + } + if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) + { + transform.right = Vector3.Lerp(transform.right, rotation * -RotationSpeed, Time.deltaTime).normalized; + } + + // Enabled/Disable player name, transform space, and parent TextMesh + if (Input.GetKeyDown(KeyCode.P)) + { + s_EnablePlayerParentingText = !s_EnablePlayerParentingText; + } + + if (Input.GetKeyDown(KeyCode.C)) + { + ContinualChildMotion = !ContinualChildMotion; + m_PlayerBallMotion.SetContinualMotion(ContinualChildMotion); + } + + m_PushMotion = Vector3.Lerp(m_PushMotion, Vector3.zero, 0.35f); + + m_PlayerBallMotion.HasMotion(moveMotion); + } + + /// + /// Updates player TextMesh relative to each client's camera view + /// + private void OnGUI() + { + if (m_ParentedText != null) + { + if (m_ParentedText.gameObject.activeInHierarchy != s_EnablePlayerParentingText) + { + m_ParentedText.gameObject.SetActive(s_EnablePlayerParentingText); + } + if (s_EnablePlayerParentingText) + { + var position = Camera.main.transform.position; + position.y = m_ParentedText.transform.position.y; + m_ParentedText.transform.LookAt(position, transform.up); + m_ParentedText.transform.forward = -m_ParentedText.transform.forward; + } + } + } + + /// + /// Updates the contents of the parented + /// + private void UpdateParentedText() + { + if (m_ParentedText) + { + m_ParentedText.color = m_PlayerColor.Color; + if (transform.parent) + { + m_ParentedText.text = $"{gameObject.name}\n Local Space\n Parent: {transform.parent.name}"; + } + else + { + m_ParentedText.text = $"{gameObject.name}\n WorldSpace\n Parent: None"; + } + } + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs.meta new file mode 100644 index 0000000000..d1b709a9fb --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5ce25b0b3f15e6446a88a85787c2f94a diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs new file mode 100644 index 0000000000..bedc3ed758 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs @@ -0,0 +1,527 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Unity.Netcode; +using Unity.Services.Authentication; +using Unity.Services.Core; +using Unity.Services.Multiplayer; +using UnityEngine; +using SessionState = Unity.Services.Multiplayer.SessionState; + +#region NetworkManagerBootstrapperEditor +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(NetworkManagerBootstrapper), true)] +[CanEditMultipleObjects] +public class NetworkManagerBootstrapperEditor : NetworkManagerEditor +{ + private SerializedProperty m_TargetFrameRate; + private SerializedProperty m_EnableVSync; + + public override void OnEnable() + { + m_TargetFrameRate = serializedObject.FindProperty(nameof(NetworkManagerBootstrapper.TargetFrameRate)); + m_EnableVSync = serializedObject.FindProperty(nameof(NetworkManagerBootstrapper.EnableVSync)); + base.OnEnable(); + } + + private void DisplayNetworkManagerBootstrapperProperties() + { + EditorGUILayout.PropertyField(m_TargetFrameRate); + EditorGUILayout.PropertyField(m_EnableVSync); + } + + public override void OnInspectorGUI() + { + var extendedNetworkManager = target as NetworkManagerBootstrapper; + void SetExpanded(bool expanded) { extendedNetworkManager.NetworkManagerBootstrapperExpanded = expanded; }; + DrawFoldOutGroup(extendedNetworkManager.GetType(), DisplayNetworkManagerBootstrapperProperties, extendedNetworkManager.NetworkManagerBootstrapperExpanded, SetExpanded); + base.OnInspectorGUI(); + } +} +#endif +#endregion + +/// +/// An extended NetworkManager to handle the bootstrap loading process specific to a client-server +/// topology where one might want to have local server-side scenes, local client-side scenes, and shared (synchronized) scenes. +/// +/// +public class NetworkManagerBootstrapper : NetworkManager +{ + #region Validation +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool NetworkManagerBootstrapperExpanded; + protected override void OnValidateComponent() + { + m_OriginalVSyncCount = QualitySettings.vSyncCount; + m_ServicesRegistered = CloudProjectSettings.organizationName != string.Empty && CloudProjectSettings.organizationId != string.Empty; + base.OnValidateComponent(); + } +#endif + #endregion + + #region Properties + public static NetworkManagerBootstrapper Instance; + + public int TargetFrameRate = 100; + public bool EnableVSync = false; + + [HideInInspector] + [SerializeField] + private int m_OriginalVSyncCount; + + /// + /// Example of how to control scene loading server local, client local, or shared. + /// Server local: nothing is synchronized with clients. + /// Client local: nothing is synchronized with the server. + /// Shared: Is synchronized with clients. + /// + private SceneBootstrapLoader m_SceneBootstrapLoader; + + private enum ConnectionStates + { + None, + Connecting, + Connected, + } + + private ConnectionStates m_ConnectionState; + + [SerializeField] + private bool m_ServicesRegistered; + private ISession m_CurrentSession; + private string m_SessionName; + private string m_ProfileName; + private Task m_SessionTask; + + #endregion + + #region Initialization and Destroy + public static string GetRandomString(int length) + { + var r = new System.Random(); + return new string(Enumerable.Range(0, length).Select(_ => (char)r.Next('a', 'z')).ToArray()); + } + + public void SetFrameRate(int targetFrameRate, bool enableVsync) + { + Application.targetFrameRate = targetFrameRate; + QualitySettings.vSyncCount = enableVsync ? m_OriginalVSyncCount : 0; + } + + private void Awake() + { + Screen.SetResolution((int)(Screen.currentResolution.width * 0.40f), (int)(Screen.currentResolution.height * 0.40f), FullScreenMode.Windowed); + SetFrameRate(TargetFrameRate, EnableVSync); + SetSingleton(); + m_SceneBootstrapLoader = GetComponent(); + } + + private async void Start() + { + OnClientConnectedCallback += OnClientConnected; + OnClientDisconnectCallback += OnClientDisconnect; + OnConnectionEvent += OnClientConnectionEvent; + + // Check to see if the project has been registered with an organization before trying to sign in + if (m_ServicesRegistered) + { + if (UnityServices.Instance != null && UnityServices.Instance.State != ServicesInitializationState.Initialized) + { + await UnityServices.InitializeAsync(); + } + if (!AuthenticationService.Instance.IsSignedIn) + { + AuthenticationService.Instance.SignInFailed += SignInFailed; + AuthenticationService.Instance.SignedIn += SignedIn; + if (string.IsNullOrEmpty(m_ProfileName)) + { + m_ProfileName = GetRandomString(5); + } + AuthenticationService.Instance.SwitchProfile(m_ProfileName); + await AuthenticationService.Instance.SignInAnonymouslyAsync(); + } + } + m_SceneBootstrapLoader.LoadMainMenu(); + } + + private void OnDestroy() + { + OnClientConnectedCallback -= OnClientConnected; + OnClientDisconnectCallback -= OnClientDisconnect; + OnConnectionEvent -= OnClientConnectionEvent; + } + #endregion + + #region Session and Connection Event Handling + private void OnClientConnectionEvent(NetworkManager networkManager, ConnectionEventData eventData) + { + LogMessage($"[{Time.realtimeSinceStartup}] Connection event {eventData.EventType} for Client-{eventData.ClientId}."); + } + + private void OnClientConnected(ulong clientId) + { + LogMessage($"[{Time.realtimeSinceStartup}] Connected event invoked for Client-{clientId}."); + } + + private void OnClientDisconnect(ulong clientId) + { + LogMessage($"[{Time.realtimeSinceStartup}] Disconnected event invoked for Client-{clientId}."); + } + + private void SignedIn() + { + AuthenticationService.Instance.SignedIn -= SignedIn; + Debug.Log($"Signed in anonymously with profile {m_ProfileName}"); + } + + private void SignInFailed(RequestFailedException error) + { + AuthenticationService.Instance.SignInFailed -= SignInFailed; + Debug.LogError($"Failed to sign in {m_ProfileName} anonymously: {error}"); + } + + private void SessionStarted() + { + OnClientStarted -= SessionStarted; + m_ConnectionState = ConnectionStates.Connected; + if (IsServer) + { + LogMessage($"Server started session."); + } + else + { + LogMessage($"Client connecting to session."); + } + } + + private void SessionStopped(bool isHost) + { + LogMessage($"NetworkManager has stopped."); + OnClientStopped -= SessionStopped; + m_ConnectionState = ConnectionStates.None; + if (IsServer) + { + ResetMainCamera(); + } + } + + private async Task ConnectThroughLiveService() + { + try + { + var options = new SessionOptions() + { + Name = m_SessionName, + MaxPlayers = 32 + }.WithDistributedAuthorityNetwork(); + + m_CurrentSession = await MultiplayerService.Instance.CreateOrJoinSessionAsync(m_SessionName, options); + return m_CurrentSession; + } + catch (Exception e) + { + LogMessage($"{e.Message}"); + Debug.LogException(e); + } + return null; + } + #endregion + + #region GUI Menu + public void StartOrConnectToDistributedAuthoritySession() + { + m_SessionTask = ConnectThroughLiveService(); + m_ConnectionState = ConnectionStates.Connecting; + LogMessage($"Connecting to session {m_SessionName}..."); + } + + private void OnUpdateGUIDisconnected() + { + var width = !m_ServicesRegistered ? 600 : 300; + GUILayout.BeginArea(new Rect(10, 10, width, 800)); + + if (NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) + { + if (!m_ServicesRegistered) + { + GUILayout.Label("Project-Settings:Services-General-Settings is not configured."); + GUILayout.Label("Distributed authority requires project to be registered with your organization's services account for authentication purposes."); + } + else + { + m_SessionName = GUILayout.TextField(m_SessionName); + + if (GUILayout.Button("Create or Connect To Session")) + { + NetworkConfig.UseCMBService = true; + OnClientStopped += SessionStopped; + OnClientStarted += SessionStarted; + m_SceneBootstrapLoader.StartSession(SceneBootstrapLoader.StartAsTypes.Client); + } + } + } + else + { + if (GUILayout.Button("Start Server")) + { + OnServerStopped += SessionStopped; + OnServerStarted += SessionStarted; + m_SceneBootstrapLoader.StartSession(SceneBootstrapLoader.StartAsTypes.Server); + } + + if (GUILayout.Button("Start Host")) + { + OnServerStopped += SessionStopped; + OnServerStarted += SessionStarted; + m_SceneBootstrapLoader.StartSession(SceneBootstrapLoader.StartAsTypes.Host); + } + + if (GUILayout.Button("Start Client")) + { + OnClientStopped += SessionStopped; + OnClientStarted += SessionStarted; + m_SceneBootstrapLoader.StartSession(SceneBootstrapLoader.StartAsTypes.Client); + } + } + GUILayout.EndArea(); + } + + private int OnUpdateGUIConnected(int yAxisOffset) + { + GUILayout.BeginArea(new Rect(10, 10, 800, 800)); + if (CMBServiceConnection) + { + GUILayout.Label($"Distributed Authority Session: {m_SessionName}"); + if (LocalClient.IsSessionOwner) + { + GUILayout.Label("[Session Owner]"); + yAxisOffset += 20; + } + } + else + { + GUILayout.Label($"Client-Server Session"); + } + GUILayout.EndArea(); + + GUILayout.BeginArea(new Rect(Display.main.renderingWidth - 160, 10, 150, 80)); + var endSessionText = IsServer && !DistributedAuthorityMode ? "Shutdown" : "Disconnect"; + if (GUILayout.Button(endSessionText)) + { + if (m_CurrentSession != null && m_CurrentSession.State == SessionState.Connected) + { + m_CurrentSession.LeaveAsync(); + m_CurrentSession = null; + } + else + { + Shutdown(); + } + } + GUILayout.EndArea(); + + return yAxisOffset; + } + + private void OnGUI() + { + var yAxisOffset = 10; + switch (m_ConnectionState) + { + case ConnectionStates.None: + { + yAxisOffset = 80; + OnUpdateGUIDisconnected(); + break; + } + case ConnectionStates.Connected: + { + yAxisOffset = OnUpdateGUIConnected(40); + break; + } + } + + GUILayout.BeginArea(new Rect(10, yAxisOffset, 600, 800)); + if (m_MessageLogs.Count > 0) + { + GUILayout.Label("-----------(Log)-----------"); + // Display any messages logged to screen + foreach (var messageLog in m_MessageLogs) + { + GUILayout.Label(messageLog.Message); + } + GUILayout.Label("---------------------------"); + } + GUILayout.EndArea(); + } + #endregion + + #region Server Camera Handling + private Vector3 m_CameraOriginalPosition; + private Quaternion m_CameraOriginalRotation; + private int m_CurrentFollowPlayerIndex = -1; + private MoverScriptNoRigidbody m_CurrentPlayerFollowed; + + private void ResetMainCamera() + { + m_CurrentFollowPlayerIndex = -1; + SetCameraDefaults(); + } + private void SetCameraDefaults() + { + if (Camera.main != null && Camera.main.transform.parent != null) + { + Camera.main.transform.SetParent(null, false); + Camera.main.transform.position = m_CameraOriginalPosition; + Camera.main.transform.rotation = m_CameraOriginalRotation; + } + } + + /// + /// Server only (i.e. not host), follow players as they move around + /// + private void ServerFollowPlayerCheck() + { + bool leftBracket = Input.GetKeyDown(KeyCode.LeftBracket); + bool rightBracket = Input.GetKeyDown(KeyCode.RightBracket); + + if ((leftBracket || rightBracket) && ConnectedClientsIds.Count > 0) + { + // Capture the main camera's original position and rotation the first time the server-side + // follows a player. + if (m_CurrentFollowPlayerIndex == -1) + { + m_CameraOriginalPosition = Camera.main.transform.position; + m_CameraOriginalRotation = Camera.main.transform.rotation; + } + + if (leftBracket) + { + m_CurrentFollowPlayerIndex--; + if (m_CurrentFollowPlayerIndex < 0) + { + m_CurrentFollowPlayerIndex = ConnectedClientsIds.Count - 1; + } + } + else + { + m_CurrentFollowPlayerIndex++; + } + + m_CurrentFollowPlayerIndex %= ConnectedClientsIds.Count; + + var playerId = ConnectedClientsIds[m_CurrentFollowPlayerIndex]; + var playerNetworkClient = ConnectedClients[playerId]; + m_CurrentPlayerFollowed = playerNetworkClient.PlayerObject.GetComponent(); + Camera.main.transform.SetParent(playerNetworkClient.PlayerObject.transform, false); + } + else if (Input.GetKeyDown(KeyCode.Backspace)) + { + ClearFollowPlayer(); + } + } + public void ClearFollowPlayer() + { + if (m_CurrentPlayerFollowed != null) + { + m_CurrentPlayerFollowed = null; + SetCameraDefaults(); + } + } + #endregion + + #region Update Methods and Properties + /// + /// General update for server-side + /// + private void ServerSideUpdate() + { + if (!IsHost) + { + ServerFollowPlayerCheck(); + } + } + + /// + /// General update for client-side + /// + private void ClientSideUpdate() + { + + } + + private void Update() + { + if (IsListening) + { + if (IsServer) + { + ServerSideUpdate(); + } + else + { + ClientSideUpdate(); + } + } + + if (m_MessageLogs.Count == 0) + { + return; + } + + for (int i = m_MessageLogs.Count - 1; i >= 0; i--) + { + if (m_MessageLogs[i].ExpirationTime < Time.realtimeSinceStartup) + { + m_MessageLogs.RemoveAt(i); + } + } + } + #endregion + + #region Message Logging + + private List m_MessageLogs = new List(); + + private class MessageLog + { + public string Message { get; private set; } + public float ExpirationTime { get; private set; } + + public MessageLog(string msg, float timeToLive) + { + Message = msg; + ExpirationTime = Time.realtimeSinceStartup + timeToLive; + } + } + + public void LogMessage(string msg, float timeToLive = 10.0f) + { + if (m_MessageLogs.Count > 0) + { + m_MessageLogs.Insert(0, new MessageLog(msg, timeToLive)); + } + else + { + m_MessageLogs.Add(new MessageLog(msg, timeToLive)); + } + + Debug.Log(msg); + } + #endregion + + public NetworkManagerBootstrapper() + { + Instance = this; + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs.meta new file mode 100644 index 0000000000..29e94329b2 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7db19e0e150e50d4ab23c69a086b8b6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs new file mode 100644 index 0000000000..7774f52d71 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs @@ -0,0 +1,65 @@ +using Unity.Netcode; +using UnityEngine; + +/// +/// Handles spawning different prefab versions based on whether it is a server or client. +/// !!! CAUTION !!! +/// Both network prefabs **MUST** have the same components +/// and any server or client specific components that are not netcode related but are +/// dependencies of a component on only the server or client +/// needs to have code within the component to account for +/// any missing dependencies. +/// +[RequireComponent(typeof(NetworkManager))] +[RequireComponent(typeof(NetworkPrefabOverrideHandler))] +public class NetworkPrefabOverrideHandler : MonoBehaviour, INetworkPrefabInstanceHandler +{ + public GameObject NetworkPrefab; + public GameObject NetworkPrefabOverride; + + private NetworkManagerBootstrapper m_NetworkManager; + + private void Start() + { + m_NetworkManager = GetComponent(); + m_NetworkManager.PrefabHandler.AddHandler(NetworkPrefab, this); + NetworkManager.OnDestroying += NetworkManager_OnDestroying; + } + + private void NetworkManager_OnDestroying(NetworkManager obj) + { + m_NetworkManager.PrefabHandler.RemoveHandler(NetworkPrefab); + } + + /// + /// Invoked on both server and clients when the prefab is spawned. + /// Server-side will spawn the default network prefab. + /// Client-side will spawn the network prefab override version. + /// + /// the client identifier that will own this network prefab instance + /// optional to use the position passed in + /// optional to use the rotation passed in + /// + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + var gameObject = m_NetworkManager.IsClient ? Instantiate(NetworkPrefabOverride) : Instantiate(NetworkPrefab); + // You could integrate spawn locations here and on the server side apply the spawn position at + // this stage of the spawn process. + gameObject.transform.position = position; + gameObject.transform.rotation = rotation; + return gameObject.GetComponent(); + } + + public void Destroy(NetworkObject networkObject) + { + // Another useful thing about handling this instantiation and destruction of a NetworkObject is that you can do house cleaning + // prior to the object being destroyed. This handles the scenario where the server is following a player and the player disconnects. + // Before destroying the player object, we want to unparent the camera and reset the player being followed. + if (m_NetworkManager.IsServer && !m_NetworkManager.IsHost && Camera.main != null && Camera.main.transform.parent == networkObject.transform) + { + m_NetworkManager.ClearFollowPlayer(); + } + Destroy(networkObject.gameObject); + } +} + diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs.meta new file mode 100644 index 0000000000..df7dd96d8e --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5c472ff64b067344893ed2e632d0f9f1 \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs new file mode 100644 index 0000000000..7e525d6c33 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using Unity.Netcode.Components; +using UnityEngine; +using System.Linq; + + +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(PlayerBallMotion), true)] +[CanEditMultipleObjects] +public class PlayerBallMotionEditor : NetworkTransformEditor +{ + private SerializedProperty m_RotationAxis; + private SerializedProperty m_RotationSpeed; + + + public override void OnEnable() + { + m_RotationAxis = serializedObject.FindProperty(nameof(PlayerBallMotion.RotationAxis)); + m_RotationSpeed = serializedObject.FindProperty(nameof(PlayerBallMotion.RotationSpeed)); + base.OnEnable(); + } + + private void DrawPlayerBallMotionProperties() + { + EditorGUILayout.PropertyField(m_RotationAxis); + EditorGUILayout.PropertyField(m_RotationSpeed); + } + + public override void OnInspectorGUI() + { + var playerBallMotion = target as PlayerBallMotion; + void SetExpanded(bool expanded) { playerBallMotion.ExpandPlayerBallMotion = expanded; }; + DrawFoldOutGroup< PlayerBallMotion>(playerBallMotion.GetType(), DrawPlayerBallMotionProperties, playerBallMotion.ExpandPlayerBallMotion, SetExpanded); + base.OnInspectorGUI(); + } +} +#endif + +public class PlayerBallMotion : NetworkTransform +{ +#if UNITY_EDITOR + public bool ExpandPlayerBallMotion; + public bool ExpandNetworkTransform; +#endif + public enum RotateAroundAxis + { + Up, + Right, + Forward + } + + public RotateAroundAxis RotationAxis; + public float RotationSpeed = 1.5f; + + private Vector3 m_AxisRotation = Vector3.zero; + private List m_Children; + + private bool m_ContinualMotion; + private float m_CurrentRotionMotion = 1.0f; + public void SetContinualMotion(bool continualMotion) + { + m_ContinualMotion = continualMotion; + foreach (var child in m_Children) + { + child.SetContinualMotion(continualMotion); + } + } + + protected override void Awake() + { + m_Children = GetComponentsInChildren().Where((c)=> c != this).ToList(); + base.Awake(); + } + + private void SetRotationAixs() + { + switch (RotationAxis) + { + case RotateAroundAxis.Up: + { + m_AxisRotation = transform.parent.up; + break; + } + case RotateAroundAxis.Right: + { + m_AxisRotation = transform.parent.right; + break; + } + case RotateAroundAxis.Forward: + { + m_AxisRotation = transform.parent.forward; + break; + } + } + } + + public void HasMotion(float direction) + { + if (direction == 0.0f) + { + if(!m_ContinualMotion) + { + return; + } + } + else + { + m_CurrentRotionMotion = RotationSpeed * direction; + } + + + transform.LookAt(transform.parent); + SetRotationAixs(); + transform.RotateAround(transform.parent.position, m_AxisRotation, m_CurrentRotionMotion); + foreach(var child in m_Children) + { + child.HasMotion(direction); + } + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs.meta new file mode 100644 index 0000000000..407e0adf7b --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerBallMotion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 792d7ce524eb358469373fe12babef88 \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs new file mode 100644 index 0000000000..aa3dc8c4ba --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Unity.Netcode; +using UnityEngine; + + +public class PlayerColor : NetworkBehaviour +{ + private static Color[] s_Colors = { Color.red, Color.green, Color.blue, Color.cyan, Color.magenta, Color.yellow }; + public bool ApplyColorToChildren; + public Color Color { get; private set; } + public List IgnoreChildren; + + public override void OnNetworkSpawn() + { + MeshRenderer meshRenderer = GetComponent(); + ulong myId = GetComponent().OwnerClientId - (ulong)(NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection ? 1 : 0); + Color = s_Colors[myId % Convert.ToUInt64(s_Colors.Length)]; + if (meshRenderer) + { + meshRenderer.material.color = Color; + if (ApplyColorToChildren) + { + var meshRenderers = GetComponentsInChildren(); + foreach (var childMeshRenderer in meshRenderers) + { + if (IgnoreChildren != null && IgnoreChildren.Contains(childMeshRenderer.gameObject)) + { + continue; + } + childMeshRenderer.material.color = Color; + } + } + } + + if (IsLocalPlayer) + { + var gameObject = FindAnyObjectByType(); + if (gameObject != null) + { + var serverHost = gameObject.GetComponent(); + serverHost?.SetColor(Color); + } + } + base.OnNetworkSpawn(); + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs.meta new file mode 100644 index 0000000000..90b6ca4628 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/PlayerColor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e5128237997be649af0cc87dd0eb563 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs new file mode 100644 index 0000000000..eaa32a77d6 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using Unity.Netcode; +using Unity.Netcode.Components; +using UnityEngine; +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(RotatingBodyLogic), true)] +[CanEditMultipleObjects] +public class RotatingBodyLogicEditor : NetworkTransformEditor +{ + private SerializedProperty m_RotationSpeed; + private SerializedProperty m_RotateDirection; + private SerializedProperty m_OnExitTransferParentOnStay; + private SerializedProperty m_PathMotion; + + + public override void OnEnable() + { + m_RotationSpeed = serializedObject.FindProperty(nameof(RotatingBodyLogic.RotationSpeed)); + m_RotateDirection = serializedObject.FindProperty(nameof(RotatingBodyLogic.RotateDirection)); + m_OnExitTransferParentOnStay = serializedObject.FindProperty(nameof(RotatingBodyLogic.OnExitTransferParentOnStay)); + m_PathMotion = serializedObject.FindProperty(nameof(RotatingBodyLogic.PathMovement)); + base.OnEnable(); + } + + private void DisplayRotatingBodyLogicProperties() + { + EditorGUILayout.PropertyField(m_RotationSpeed); + EditorGUILayout.PropertyField(m_RotateDirection); + EditorGUILayout.PropertyField(m_OnExitTransferParentOnStay); + EditorGUILayout.PropertyField(m_PathMotion); + } + + public override void OnInspectorGUI() + { + var rotatingBodyLogic = target as RotatingBodyLogic; + void SetExpanded(bool expanded) { rotatingBodyLogic.RotatingBodyLogicExpanded = expanded; }; + DrawFoldOutGroup(rotatingBodyLogic.GetType(), DisplayRotatingBodyLogicProperties, rotatingBodyLogic.RotatingBodyLogicExpanded, SetExpanded); + base.OnInspectorGUI(); + } +} +#endif + +/// +/// Handles rotating the large in-scene placed platform/tunnels and parenting/deparenting players +/// +public class RotatingBodyLogic : NetworkTransform +{ +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool RotatingBodyLogicExpanded; +#endif + + public enum RotationDirections + { + Clockwise, + CounterClockwise + } + + [Range(0.0f, 2.0f)] + public float RotationSpeed = 1.0f; + public RotationDirections RotateDirection; + public RotatingBodyLogic OnExitTransferParentOnStay; + public List PathMovement; + + private TagHandle m_TagHandle; + private float m_RotationDirection; + + private int m_CurrentPathObject = -1; + private GameObject m_CurrentNavPoint; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_TagHandle = TagHandle.GetExistingTag("Player"); + m_RotationDirection = RotateDirection == RotationDirections.Clockwise ? 1.0f : -1.0f; + SetNextPoint(); + base.OnNetworkPreSpawn(ref networkManager); + } + + private void SetNextPoint() + { + if (PathMovement == null || PathMovement.Count == 0) + { + return; + } + m_CurrentPathObject++; + m_CurrentPathObject %= PathMovement.Count; + m_CurrentNavPoint = PathMovement[m_CurrentPathObject]; + } + + + /// + /// When triggered, the player is parented under the rotating body. + /// + /// + /// This is only triggered on the owner side since we disable the CharacterController + /// on all non-owner instances. + /// + private void OnTriggerEnter(Collider other) + { + if (!IsSpawned || !other.CompareTag(m_TagHandle)) + { + return; + } + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null) + { + nonRigidPlayerMover.SetParent(NetworkObject); + } + } + + // This is used to handle NetworkObject to NetworkObject parenting detection + private List m_TriggerStayBodies = new List(); + + private void OnTriggerStay(Collider other) + { + if (!IsSpawned || !other.CompareTag(m_TagHandle)) + { + return; + } + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null) + { + if (!m_TriggerStayBodies.Contains(nonRigidPlayerMover)) + { + m_TriggerStayBodies.Add(nonRigidPlayerMover); + } + } + } + + internal bool HandleParentingForTriggerStayBodies(MoverScriptNoRigidbody moverScriptNoRigidbody) + { + if (m_TriggerStayBodies.Contains(moverScriptNoRigidbody)) + { + moverScriptNoRigidbody.SetParent(NetworkObject); + return true; + } + return false; + } + + /// + /// When triggered, the player is deparented from the rotating body. + /// + /// + /// This is only triggered on the owner side since we disable the CharacterController + /// on all non-owner instances. + /// + private void OnTriggerExit(Collider other) + { + if (!IsSpawned || !other.CompareTag(m_TagHandle)) + { + return; + } + + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null) + { + m_TriggerStayBodies.Remove(nonRigidPlayerMover); + if (OnExitTransferParentOnStay && OnExitTransferParentOnStay.HandleParentingForTriggerStayBodies(nonRigidPlayerMover)) + { + return; + } + // Otherwise, set parent back to root + nonRigidPlayerMover.SetParent(null); + } + } + + /// + /// We rotate the body during late update to avoid fighting between the host/owner (depending upon network topology) + /// motion and the body's motion/rotation. + /// + private void LateUpdate() + { + if (!IsSpawned || !CanCommitToTransform) + { + return; + } + + if (m_CurrentNavPoint != null) + { + if (Vector3.Distance(m_CurrentNavPoint.transform.position, transform.position) <= 0.05f) + { + SetNextPoint(); + } + + var direction = (m_CurrentNavPoint.transform.position - transform.position).normalized; + transform.position = Vector3.Lerp(transform.position, transform.position + direction * 10, Time.deltaTime); + } + + if (RotationSpeed > 0.0f) + { + transform.right = Vector3.Lerp(transform.right, transform.forward * m_RotationDirection, Time.deltaTime * RotationSpeed); + } + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs.meta new file mode 100644 index 0000000000..ce0ea09165 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/RotatingBodyLogic.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 374ac199eb18f0f468bc018a722775c2 \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs new file mode 100644 index 0000000000..08642c10e1 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs @@ -0,0 +1,417 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using Unity.Netcode; + +using UnityEngine; +using UnityEngine.SceneManagement; +#if UNITY_EDITOR +using Unity.Netcode.Editor; +using UnityEditor; + +/// +/// The custom editor for the component. +/// +[CustomEditor(typeof(SceneBootstrapLoader), true)] +[CanEditMultipleObjects] +public class SceneBootstrapLoaderEditor : NetcodeEditorBase +{ + private SerializedProperty m_ServerSceneDefines; + private SerializedProperty m_ClientSceneDefines; + + public override void OnEnable() + { + m_ServerSceneDefines = serializedObject.FindProperty(nameof(SceneBootstrapLoader.ServerSceneDefines)); + m_ClientSceneDefines = serializedObject.FindProperty(nameof(SceneBootstrapLoader.ClientSceneDefines)); + base.OnEnable(); + } + + private void DisplaySceneBootstrapLoaderProperties() + { + var sceneBootstrapLoader = target as SceneBootstrapLoader; + var networkManager = sceneBootstrapLoader.GetComponent(); + if (networkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + EditorGUILayout.PropertyField(m_ServerSceneDefines); + } + EditorGUILayout.PropertyField(m_ClientSceneDefines); + } + + public override void OnInspectorGUI() + { + var sceneBootstrapLoader = target as SceneBootstrapLoader; + void SetExpanded(bool expanded) { sceneBootstrapLoader.SceneBootstrapLoaderExpanded = expanded; }; + DrawFoldOutGroup(sceneBootstrapLoader.GetType(), DisplaySceneBootstrapLoaderProperties, sceneBootstrapLoader.SceneBootstrapLoaderExpanded, SetExpanded); + base.OnInspectorGUI(); + } +} + +#endif + +/// +/// Example of how to control scene loading using a additive client +/// synchronization mode and server-side scene validation approach. +/// +/// +/// This component should be added to the same GameObject that the component +/// is attached to (for this example we extended to ). +/// +[RequireComponent(typeof(NetworkManager))] +[RequireComponent(typeof(SceneBootstrapLoader))] +public class SceneBootstrapLoader : MonoBehaviour +{ +#if UNITY_EDITOR + // Inspector view expand/collapse settings for this derived child class + [HideInInspector] + public bool SceneBootstrapLoaderExpanded; + + [Tooltip("The main menu or out of session scene to load.")] + public SceneAsset MainMenuSceneAsset; + + /// + /// Converts SceneAssets to scene names + /// + private void OnValidate() + { + if (MainMenuSceneAsset != null) + { + m_MainMenuScene = MainMenuSceneAsset.name; + } + var networkManager = GetComponent(); + if (networkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + ServerSceneDefines.Validate(); + } + ClientSceneDefines.Validate(); + } +#endif + + [Tooltip("Defines how to handle scenes for the server instance.")] + public ServerSceneDefine ServerSceneDefines; + [Tooltip("Defines how to handle scenes for the client instance.")] + public SceneDefine ClientSceneDefines; + + private NetworkManagerBootstrapper m_NetworkManager; + private string m_SceneCurrentlyLoading; + private string m_SceneJustLoaded; + + [HideInInspector] + [SerializeField] + private string m_MainMenuScene; + + private void Awake() + { + m_NetworkManager = GetComponent(); + } + + /// + /// Should be invoked by bootstrap when first starting the applicaiton and should be loaded upon exiting + /// a session and shutting down the . + /// + public void LoadMainMenu() + { + if (!m_NetworkManager.IsListening) + { + SceneManager.LoadScene(m_MainMenuScene, LoadSceneMode.Single); + } + else + { + Debug.LogWarning($"Trying to load main scene {m_MainMenuScene} while {nameof(NetworkManagerBootstrapper)} is still running!"); + } + } + + public enum StartAsTypes + { + Server, + Host, + Client + } + + /// + /// Invoked by the when + /// starting a client or server. + /// + /// + public void StartSession(StartAsTypes startAsType) + { + StartCoroutine(PreSceneLoading(startAsType)); + } + + /// + /// Both clients and the server invoke this when they shutdown. + /// + private void OnNetworkManagerShutdown(bool wasHost) + { + LoadMainMenu(); + } + + #region SCENE PRE & POST START LOADING METHODS + + + + private IEnumerator PreSceneLoading(StartAsTypes startAsType) + { + var sceneDefines = startAsType == StartAsTypes.Client ? ClientSceneDefines : ServerSceneDefines; + SceneManager.sceneLoaded += SceneLoaded; + + // Unloads any currently loaded scenes and becomes the default active scene. + // The default active scene ** has to be shared ** because it is where GameObjects are + // instantiated (which includes where network prefabs are instantiated when spawned dynamically) + yield return HandleSceneLoading(sceneDefines.DefaultActiveScene, LoadSceneMode.Single); + + // Now load our local server or clien scenes additively + foreach (var sceneName in sceneDefines.LocalScenes) + { + yield return HandleSceneLoading(sceneName, LoadSceneMode.Additive); + } + + // When running as a host, we load the client scenes too + if (startAsType == StartAsTypes.Host) + { + foreach (var sceneName in ClientSceneDefines.LocalScenes) + { + yield return HandleSceneLoading(sceneName, LoadSceneMode.Additive); + } + } + + SceneManager.sceneLoaded -= SceneLoaded; + + // Now start the NetworkManager (server or client) + if (startAsType != StartAsTypes.Client && m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer) + { + // Server needs to do some additional congiruations to ignore the local scene loaded and + // will load any additional shared and synchronized scenes via the NetworkSceneManager. + m_NetworkManager.OnServerStarted += OnServerStarted; + m_NetworkManager.OnServerStopped += OnNetworkManagerShutdown; + if (startAsType == StartAsTypes.Server) + { + m_NetworkManager.StartServer(); + } + else + { + m_NetworkManager.StartHost(); + } + } + else + { + m_NetworkManager.OnClientStopped += OnNetworkManagerShutdown; + + // Distributed authority needs to start using the service and needs to know when the client becomes + // the session owner. + if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) + { + m_NetworkManager.OnSessionOwnerPromoted += OnSessionOwnerPromoted; + m_NetworkManager.StartOrConnectToDistributedAuthoritySession(); + } + else + { + m_NetworkManager.StartClient(); + } + } + } + + /// + /// When the session owner is promoted, it needs to configure itself for the additive client synchronization + /// and handle scene validations. + /// + /// + private void OnSessionOwnerPromoted(ulong sessionOwnerPromoted) + { + if (sessionOwnerPromoted == m_NetworkManager.LocalClientId) + { + // When we set the client synchronization mode to additive, the session owner will include this setting + // setting when synchronizing a newly joining client and the client will use any already loaded scenes + // that the session owner determines should be synchronized. If a scene that is being synchronized is not + // yet loaded, then the client will load that scene. + m_NetworkManager.SceneManager.SetClientSynchronizationMode(LoadSceneMode.Additive); + + // Add a callback to control which scene the session owner synchronizes with clients + m_NetworkManager.SceneManager.VerifySceneBeforeLoading = SessionOwnerVerifySceneShouldBeSynchronized; + } + } + + /// + /// Handles scene loading via or depending upon + /// whether the is started or not. + /// + private IEnumerator HandleSceneLoading(string sceneName, LoadSceneMode loadSceneMode) + { + m_SceneJustLoaded = string.Empty; + m_SceneCurrentlyLoading = sceneName; + + // Server will be the only NetworkManager instance where this is true + if (m_NetworkManager.IsListening) + { + + var loadingStatus = m_NetworkManager.SceneManager.LoadScene(sceneName, loadSceneMode); + if (loadingStatus != SceneEventProgressStatus.Started) + { + Debug.LogError($"{nameof(NetworkSceneManager)} attempted to load scene {sceneName} but returned a status of {loadingStatus}!"); + yield break; + } + } + else // Client and Server will both use this to preload their local scenes + { + SceneManager.LoadScene(m_SceneCurrentlyLoading, loadSceneMode); + } + while (m_SceneJustLoaded != m_SceneCurrentlyLoading) + { + yield return null; + } + } + + private void SceneLoaded(Scene scene, LoadSceneMode loadSceneMode) + { + m_SceneJustLoaded = scene.name; + } + #endregion + + #region SERVER POST START CONFIGURATION AND ADDITIONAL SHARED SCENE LOADING + /// + /// Starts the as a server. + /// + private void OnServerStarted() + { + m_NetworkManager.OnServerStarted -= OnServerStarted; + + // When we set the client synchronization mode to additive, the server will include this setting + // when synchronizing a client and the client will use any already loaded scenes that the server + // wants to synchronize. If a scene that is being synchronized is not yet loaded, then the client + // will load that scene. + m_NetworkManager.SceneManager.SetClientSynchronizationMode(LoadSceneMode.Additive); + + // Add a callback to control which scene the server synchronizes with clients + m_NetworkManager.SceneManager.VerifySceneBeforeLoading = ServerVerifySceneShouldBeSynchronized; + + // If we have any additional shared scenes we want to load, then load them via NetworkSceneManager + if (ServerSceneDefines.SharedScenes.Count > 0) + { + StartCoroutine(ServerLoadAdditionalSharedScenes()); + } + } + + /// + /// When a client is first synchronized, the server will determine what scenes it should synchronize with the + /// client. This callback is invoked for every scene currently loaded and if it returns false then it will not + /// attempt to synchronize the scene with the client being synchronized. + /// + private bool ServerVerifySceneShouldBeSynchronized(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) + { + return !ServerSceneDefines.LocalScenes.Contains(sceneName); + } + + /// + /// When a client is first synchronized, the session owner will determine what scenes it should synchronize with the + /// client. This callback is invoked for every scene currently loaded and if it returns false then it will not + /// attempt to synchronize the scene with the client being synchronized. + /// + /// + /// We use the for distributed authority to just exclude scenes that are already preloaded. + /// This is optional and semi-redundant since in a distributed network topology state is mirrored across all clients and + /// having locally loaded scenes not synchronized just limits where spawned objects can be migrated. + /// This is just for example purposes in the event you might need platform specific scenes loaded on a per client basis and/or + /// have some other project specific need to have locally loaded scenes that are not synchronized between all connected clients. + /// The recommended design for a distributed authority network topology is to just synchronize all scenes if possible. + /// + private bool SessionOwnerVerifySceneShouldBeSynchronized(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) + { + return !ClientSceneDefines.LocalScenes.Contains(sceneName); + } + + + private IEnumerator ServerLoadAdditionalSharedScenes() + { + m_NetworkManager.SceneManager.OnLoadEventCompleted += Server_OnLoadEventCompleted; + + foreach (var sharedScene in ServerSceneDefines.SharedScenes) + { + yield return HandleSceneLoading(sharedScene, LoadSceneMode.Additive); + } + } + + private void Server_OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) + { + m_SceneJustLoaded = sceneName; + } + #endregion +} + +/// +/// The server scene defines included in (i.e. ) and this provides +/// you with the ability to have additional additively loaded scenes that the server will +/// synchronize with clients. +/// +[Serializable] +public class ServerSceneDefine : SceneDefine +{ + [HideInInspector] + public List SharedScenes; + +#if UNITY_EDITOR + [Tooltip("These scenes will be loaded additively upon starting a session and will be synchronized.")] + public List SharedSceneAssets; + protected override void OnValidate() + { + SharedScenes = new List(); + foreach (var sharedSceneAsset in SharedSceneAssets) + { + if (sharedSceneAsset != null) + { + SharedScenes.Add(sharedSceneAsset.name); + } + } + base.OnValidate(); + } +#endif +} + +/// +/// The base client and server scenes loaded prior to starting: +/// : Must be synchonized/shared and is the default active scene. +/// : Any client or server specific scenes you don't want synchronized, but +/// want either the clients or server to load prior to starting a session. +/// +[Serializable] +public class SceneDefine +{ + [HideInInspector] + public string DefaultActiveScene; + [HideInInspector] + public List LocalScenes; + +#if UNITY_EDITOR + [Tooltip("This scene is synchronized and the default active scene loaded as LoadSceneMode.Single")] + public SceneAsset DefaultActiveSceneAsset; + [Tooltip("These scenes will be loaded additively and will not be synchronized.")] + public List LocalSceneAssets; + + /// + /// Invoked by . + /// + /// + /// This is not the same as the Unity MonoBehaviour's OnValidate + /// + protected virtual void OnValidate() + { + DefaultActiveScene = string.Empty; + if (DefaultActiveSceneAsset) + { + DefaultActiveScene = DefaultActiveSceneAsset.name; + } + LocalScenes = new List(); + foreach (var localSceneAsset in LocalSceneAssets) + { + if (localSceneAsset != null) + { + LocalScenes.Add(localSceneAsset.name); + } + } + } + + public void Validate() + { + OnValidate(); + } +#endif +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs.meta new file mode 100644 index 0000000000..adfb1bb99a --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c4251d3d650053a419a5c503babb13a6 \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs new file mode 100644 index 0000000000..93e150b2ba --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs @@ -0,0 +1,85 @@ +using Unity.Netcode; +using UnityEngine; +using UnityEngine.UI; + +public class ServerHostClientText : NetworkBehaviour +{ + private Text m_DisplayText; + + private Color m_Color; + private Color m_ColorAlpha; + private Vector3 m_AnchoredPosition; + + public void SetColor(Color color) + { + m_Color = color; + m_ColorAlpha = color; + m_ColorAlpha.a = 0.35f; + } + + private void Awake() + { + m_AnchoredPosition = (transform as RectTransform).anchoredPosition3D; + m_DisplayText = GetComponent(); + } + + private void Start() + { + if (m_DisplayText != null) + { + m_DisplayText.text = string.Empty; + SetColor(m_DisplayText.color); + } + } + + public override void OnNetworkSpawn() + { + if (m_DisplayText != null) + { + if (NetworkManager.IsServer) + { + m_DisplayText.text = NetworkManager.IsHost ? "Host" : "Server"; + if (!NetworkManager.IsHost) + { + SetColor(Color.white); + m_ColorAlpha.a = 0.65f; + } + } + else if (NetworkManager.IsClient) + { + m_DisplayText.text = $"Client-{NetworkManager.LocalClientId}"; + } + } + (transform as RectTransform).anchoredPosition3D = m_AnchoredPosition; + base.OnNetworkSpawn(); + } + + public override void OnNetworkDespawn() + { + if (m_DisplayText != null) + { + m_DisplayText.text = string.Empty; + } + base.OnNetworkDespawn(); + } + + private bool m_LastFocusedValue; + private void OnGUI() + { + if (!IsSpawned || m_LastFocusedValue == Application.isFocused) + { + return; + } + + m_LastFocusedValue = Application.isFocused; + + if (m_LastFocusedValue) + { + m_DisplayText.color = m_Color; + } + else + { + m_DisplayText.color = m_ColorAlpha; + } + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs.meta new file mode 100644 index 0000000000..081f5a96a8 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6637cd674efb56a48a3d4d545d23a8d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs new file mode 100644 index 0000000000..ebc84d8d98 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs @@ -0,0 +1,27 @@ +using Unity.Netcode; +using UnityEngine; +using UnityEngine.UI; + +public class ServerInfoDisplay : MonoBehaviour +{ + public Text ServerTime; + public Text PlayerCount; + + private void OnGUI() + { + if (!NetworkManager.Singleton || !NetworkManager.Singleton.IsListening) + { + return; + } + + if (ServerTime) + { + ServerTime.text = $"NetworkTick: {NetworkManager.Singleton.ServerTime.Tick}"; + } + + if (PlayerCount) + { + PlayerCount.text = $"Player Count: {NetworkManager.Singleton.ConnectedClients.Count}"; + } + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs.meta new file mode 100644 index 0000000000..8fcedfa9ff --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d370147e4c421014cb5dd4eee3b6a373 \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs new file mode 100644 index 0000000000..ade22f3ed6 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs @@ -0,0 +1,63 @@ +using UnityEngine; + +/// +/// This helper class is used to push a player away from a rotating body. +/// s without a don't +/// handle collision with rotating bodies. This simulates a "collision". +/// +public class TriggerPush : MonoBehaviour +{ + public enum RightOrLeft + { + Right, + Left + } + + [Tooltip("Determines if this trigger will push the player to the left or right of the root transform")] + public RightOrLeft PushDirection; + + private TagHandle m_TagHandle; + + private void Awake() + { + m_TagHandle = TagHandle.GetExistingTag("Player"); + } + + private void PushObject(Collider other, bool isInside = false) + { + var nonRigidPlayerMover = other.GetComponent(); + if (nonRigidPlayerMover != null && nonRigidPlayerMover.CanCommitToTransform) + { + // We determine the direction to push and if within a trigger we push a little more to prevent from + // completely clipping through the object. + var direction = (PushDirection == RightOrLeft.Right ? 1.0f : -1.0f) * (isInside ? 1.75f : 1.0f); + nonRigidPlayerMover.PushAwayFrom(transform.parent.right * direction); + } + } + + /// + /// Pushes the player away from the object + /// + private void OnTriggerEnter(Collider other) + { + if (!other.CompareTag(m_TagHandle)) + { + return; + } + PushObject(other); + } + + /// + /// When the trigger is in a "stay" state, we need to signal that + /// the amount to "push away" should be increased. + /// + /// + private void OnTriggerStay(Collider other) + { + if (!other.CompareTag(m_TagHandle)) + { + return; + } + PushObject(other, true); + } +} diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs.meta b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs.meta new file mode 100644 index 0000000000..7c07b1ebfc --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/TriggerPush.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2c40721ca0fd31645a742e5ad0e0cdc5 \ No newline at end of file diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures.meta b/Examples/OverridingScenesAndPrefabs/Assets/Textures.meta new file mode 100644 index 0000000000..84caff0700 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Textures.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 22c34a08d52a0644fae5e90dbcc0ba52 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/GridPattern.png b/Examples/OverridingScenesAndPrefabs/Assets/Textures/GridPattern.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f648aacc6ce68697cb397decbeb4207aecbd0a GIT binary patch literal 3152344 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 literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/GridPattern.png.meta b/Examples/OverridingScenesAndPrefabs/Assets/Textures/GridPattern.png.meta new file mode 100644 index 0000000000..305bd2392c --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Textures/GridPattern.png.meta @@ -0,0 +1,153 @@ +fileFormatVersion: 2 +guid: a092c5fa8c60ed04aa1d72555f1740bc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Windows Store Apps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_BaseMap.png b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_BaseMap.png new file mode 100644 index 0000000000000000000000000000000000000000..3f311d2225e026025a38ecae433481f37b4ba2e1 GIT binary patch literal 8210 zcmeHLeM}qY8GoUqkaQ@Cx~=IF!OiK`P0jf}{C&fiuY|CLj|HTBjE0==&ftN4j%~o0 z#*lVux=n>mU6rk4Gt;DwuA;O-30)YqyJ%$-S;#~O%VXQaUdZU{!G^W{(bnf(X{uF z1%O8$75-zuD^1$~h|kj(9kd?I-A6IJK}@r}mJ{0yCP)oHipFN5nR3pGXt`3oQ7xM2 zbcql>s}>!Q<>I-fOs-77*KXzt?RiCvy_`|8B26liVxu4f181cXo1wyJp=@eVh%W{2 zg=I{Hgh;IAYEil%5ILAzfMoJ!4v~v-l)-U1qELwmxl|#O>_SMKBru%B2pLKcl#HYZ z2@?4eL1|`INA1tr6Ojd<)S@!0)kI-fb#=A4S|aAnr5K@7sRSM*i9!n0Qe(8zHq>a@ zxyT`lvoL18$*Sj#h`^E7@|9M#2x=OJU@%458Z8ksL4#p7+Jq5eT!0iJWSJkK-1>tMrGVq7lR=x!eZTB1z|)Ca$9Ok zQH_bi_H!1#(#&wXtDu@Y1vDlq)6CIU-dx1<6^o84SX3DyNil(Z?SS6M^3|5FFWbOn z(N<0^61;}uB#KLma3v)rDN^F_L+vEX*K;y63(UMuUjbiO_Gf1xIlD9E zgi0<)A;yrpxw%x1(PE{I440Fo7D3~P^?H_)Nk|bEa;}0_Eaa$@$ygR=RH%Z(wWvfTm7}zlCQ&7WOKGWuRNzWZ2>T_;i(Jwn zJ`41?C6ZPdbZO+SLZ7DfHzhW(BsI@)o-4&Z;vsE9Z1NC8}c-Qk2w6q5n7*=7^l&NRq)h z9SLJNtkX)>39^WgQ&K4#K^ln*GSd3)YX@uKoi-Ql*CdnD0oy@QW`iJHU0ND23oUHUBThsVI z-gB`d?WAtl2oBR>uNU(gDpHcF?B-3B&g^gn5FFZNAt6 zgYXCI(y8yao_RGJfDcn(4!crQoW1XU8z}oeHme#uK}Y`PSodpl4+|;x&iwS8EoINO zqnF03+Wor*vCg51lWjq~+jYU$W9<0(5G1-!^7L-PAYzt_p6k#!02b%j zkm~azGtXT7%R}zh#HV_W?U?Y_+P8o_ueW=++e47?h5d(5_5AT_eCrv<#N?t`(>#^eRa?di6Gi3xs?pKUr`7h5zO*RZ|##CYQm+uWn2&UwY> z3Vil1UHqHDbZ7EAFk#ky55F57qa`yIocpNc^1#5r&4hjC&=AvpJlyn85NAt#6Rw54 zM)wn;3Yj|i7{ni*hwh`1W_5E^mbZ>jGrRW7qo+ zM}VC=eij2uI75%?1?rMNXWsJ9d;jUIn}2-A1^}i@rnWam(>#i}2Z$sLZQ7;^biQ0)3 z%W%uuJB;0fe>}CDGuJhN7GxZ5%%w|U_1x@^={02&eV&e`HlDp zue5uHG>6*TG!Acfhh}u@{DM#m@_UCZ#w3sjlfJ=Ya}D)92TZ?h)1+#W8h@<%>FNG+ z#bRG-S*y49q;sxp&?$R6=u!ULA0TGWXachyT}j>Cw7|{Y<*2J0bo3361s-jto-}>% z;iihZ;%7D-(FAU~==#$Gqq1+*k8hrxn}lU&eMD5GVu0bT;L=wXke(Y@d+>CxkVrz| zc<=A2tZJTvlfvcoGaQ8vOApSMkm{}@zsWv&K`0tWpZAnpyN(5#;1KAycfAUi1vf8S zhH-trpBcmc{p_Uie<@HaP8!Ke#Uk`E!o6+qytK{o$s04tmu^0+>38F{+on@7~pksxPxUe+5KN1y#KM4*e` literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_BaseMap.png.meta b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_BaseMap.png.meta new file mode 100644 index 0000000000..3a05497054 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_BaseMap.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: e71f43865e91e6b418bd0d67be2445dc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Emissive.png b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Emissive.png new file mode 100644 index 0000000000000000000000000000000000000000..df81ac1268d54bf0f6eb188573559529a04414c9 GIT binary patch literal 8225 zcmeGgdr%YU{X?;!Ry}Jw$DE$5VcOo+ZuUXmC5Z?WnK43<7O~X0>~0_jWJ40x|3|r|5XXAp*{&o{z{xMj%2+(r@!3SoOU7OR$;A zx8HvI`+o1&_x&+zQ~HucZ!7`;EXmlAnhiiSoJND#dGPVj`mfL7W1(?Fo*96}EBJpD z*nc1nfS67CoGq3unvDvYD-}`J3VP^Gt^T(KAXx zu2_R>j45o9enYj1-CVsXhpsNB<%~eBLXs*K5J4$xp^(bb5`$S$sTBC}D&U-7#sr8T z#8RvjB=ZE3Et)JOg)^~;M2Mp_j!O`!Tu4Yn5|Qku2#J#fh7%Y;qJ&r>CKaR{3H}6- znu*aVvQvK^qy^uUf+CB>sKBs_iV9&xqL4EcVuV~S$8ZuONfbh$<|>1QszeRugb;>Q z)=Zo9MvI;^AUsB@fGf8s1(4H#2&Kj_T7x;LCa5s1k}_h15a&hm12S|N&RA|L@e^lg zj4feHS%bw4VTmxTv52#9<|6JHsKcLU5P+)HXu>p}%1dc!n1tDqwgZY0RLE1I%{f&@ z7RzSMT)By6({?~M6L@KiiWCz|SvXS;$CZQ(l@($cAxR;Dyp^jrFkFTC?T8I*DrI4n z0^VvUPNKLd2bU>C&}!nfIHADt0H}sz^t!5TI1?lcxl4>(&qzthGI2V63H)HrPG65?q@_p*xkQ3O z8U5^PG>Qy^*+Lm;HX~IjfXWf-^^77>L^3itl#@<`l8PCT45cKL4AmvlGWe4+GMo)VoJ?k>^9t-KqZ_7h9Od%x=0^UF*GmmR79!Tc$gs4@E&Z~kRt zt;KhG$Qc8`m50suq5x=k6blP->~c7~CV&I-pF6(#N>y`HC;iU%Tk>tWHQmQN$NGnN ztm?FBJ6d9>-`TY~H{0vD&hgNxa9H;TI5fNlk%2XQQ?Q;A57XXkwCyjd0a8#^>F?MI7tYgS(g>ky`AHdjRv)Syj_KuF$n5bJh_4A?T)~J1B z6C;(oKRbWWG4_r#@1%CenSrHRH`Qp}H@x>Zw%iUr_GeqnKHDV@{kmAw1i*pL>u}@+ z%R1%yV09FbH64zhWr^t(7cywQp8YedchKNeLdQ3*&Q;Hz67y_J2868mB)=scdhR7v zwXtEOwdv91wST!Y-$Uz7r)$VJTC2MA6^w7KR@+%tYqe%B zn|JQRdavV=eI#z*&bNz?c6Vpi#DCm#qb5^%W4v&Q!6$2kb$fUHaCf`+bYE}0+6&RO zlAfN|AOE`c-QI4MCo}HggR$yMOSijs4ytSCTHKtgiUyWX zJC1dDCt>CPk=)Ck)Q%6fgG2o_nN8>7`Z4#NW@}6O_CNbO9Ov+2XupWYXqFeqSELKz zEmmH9G}zvB4fekdrn?>gYpBtkJNBG{9esy-%HtY>-2ebSF4O|JF1TDS`^ewkcyD~>s9YsDfP94wpl=<8F`{RSx5@2spHU`Jp+}cOv-Dy_{cDi#> zNWVwl7w+vEjoqEc_d(FAUHKl?c^m`xx7m8(avw}_k;`+%z5&sDw$60i&xg;prUP)< zejkpr^n4oO#1Zy|{@jUt-gix|9WuT2cya&@!iktX|AFc3DHut7dc-e|ujv}Wj(d)|#^EXR<-g{5VIE(l`;e~N zq;ekZ&l-uVd}OY*&;4L=LtC!{9 zD-J4tY`l4EQHeFbcEL8adsIy|9D^$VS;O!$c*vPE(G?)?UBwOW$hW0{5k__NZc;%iu?l Nk+v!IqxIW=`(MOlo)iE8 literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Emissive.png.meta b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Emissive.png.meta new file mode 100644 index 0000000000..bcfe13d8d7 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Emissive.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: 8bf2cf149563066489e749ea032dbca7 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Normal.png b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Normal.png new file mode 100644 index 0000000000000000000000000000000000000000..1d060fba6157919ffa347e9d580c5303191c7f67 GIT binary patch literal 21103 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 Ncdx%xefjk_{|^NY)U*Hq literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Normal.png.meta b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Normal.png.meta new file mode 100644 index 0000000000..4a5b2ad61c --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_01_Normal.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: b94463ba36040ec4082132c54dd1bbad +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 0 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 1 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_02_BaseMap.png b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_02_BaseMap.png new file mode 100644 index 0000000000000000000000000000000000000000..b3069b03d14bb6dd04f8050c0378123250ce6c76 GIT binary patch literal 9489 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 G%6|c^ch-ae literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_02_BaseMap.png.meta b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_02_BaseMap.png.meta new file mode 100644 index 0000000000..7fbedef673 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Textures/Grid_02_BaseMap.png.meta @@ -0,0 +1,130 @@ +fileFormatVersion: 2 +guid: 844faea9c35b2464c8ecb09f66e2e2f8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Images/EndResultView.png b/Examples/OverridingScenesAndPrefabs/Images/EndResultView.png new file mode 100644 index 0000000000000000000000000000000000000000..b98d38ee98f0b9f5e87bea4d7a0a4fa3691b4454 GIT binary patch literal 383115 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

crMd(Oyu;XbgSaa66Z8k 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 LNkvXXu0mjf8>E^2 literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Images/NetworkManagerBootstrapper.png b/Examples/OverridingScenesAndPrefabs/Images/NetworkManagerBootstrapper.png new file mode 100644 index 0000000000000000000000000000000000000000..21a39012e905ccc2d7b8a188eab85313222998e5 GIT binary patch literal 41553 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

Pt{n|38^H9|Rj+ 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 literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Images/ScenesView.png b/Examples/OverridingScenesAndPrefabs/Images/ScenesView.png new file mode 100644 index 0000000000000000000000000000000000000000..49dcc587b65b9c15ad23c99d7fde15a358057b56 GIT binary patch literal 18823 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 Ouk3A{tQ#ze@&5ol@h<=X literal 0 HcmV?d00001 diff --git a/Examples/OverridingScenesAndPrefabs/Images/ServiceSettings.png b/Examples/OverridingScenesAndPrefabs/Images/ServiceSettings.png new file mode 100644 index 0000000000000000000000000000000000000000..ea7d2729d2eaf3e98a93732ac7818638f13046a6 GIT binary patch literal 71789 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/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%0D=Q02w7#&FH&p^# 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

h-$s8HkJ#@BB=t1@a2c)eRx{U`D>Jbli% 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 Overriding Scenes and NetworkPrefabs +![image](Images/EndResultView.png) +_Supports using the client-server and distributed authority network topologies._ + + +This example, based on the [Netcode for GameObjects Smooth Transform Space Transitions](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/tree/example/server-client-unique-scenes-and-prefabs/Examples/CharacterControllerMovingBodies), provides and overview of how to use: +- [`NetworkPrefabHandler`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/api/Unity.Netcode.NetworkPrefabHandler.html) as a way to dynamically control overriding network prefabs and how they are instantiated. + - For this example, the prefab handler is overriding the player prefab. + - *You will only see the end result of this portion of the example by running a server instance (i.e. not host) as that will create instances of the ServerPlayer network prefab instead of the ClientPlayer network prefab.* +- [`NetworkSceneManager.SetClientSynchronizationMode`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/api/Unity.Netcode.NetworkSceneManager.html#Unity_Netcode_NetworkSceneManager_SetClientSynchronizationMode_UnityEngine_SceneManagement_LoadSceneMode_) to change the default client synchronization mode (SingleMode) to an additive client synchronization mode. + - Additive client synchronization mode will prevent already existing preloaded scenes from being unloaded and will use them, as opposed to reloading the same scene, during a client's initial synchronization. + - *This is a server-side only setting that gets sent to the clients during the initial synchronization process.* +- [`NetworkSceneManager.VerifySceneBeforeLoading`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/api/Unity.Netcode.NetworkSceneManager.html#Unity_Netcode_NetworkSceneManager_VerifySceneBeforeLoading) in order to control what scenes the server will include when sending the synchronization message to newly connecting clients. + +## Building The Project +This example uses unity services. Upon loading the project for the first time, you will want to set your organization and create a new cloud project. This is the only required setting to create stand alone builds for this project. +![image](Images/ServiceSettings.png) + +## Terminology + +### Shared Scenes +These are scenes that will be synchronized between a server or session owner and used when a client runs through the initial synchronization process. +- You can populate these scenes with in-scene placed or dynamically spawned NetworkObjects. +- These scenes **must be** within the scenes in build list. + +### Local Scenes +These are scenes that are always only local to the application instances (server or client) and will not be synchronized. +- You should not populate these scenes with NetworkObjects. + -However, this example includes one of several ways you can associate a `MonoBehaviour` with a `NetworkBehaviour`. +- These scenes can be dynamically created, included in the scenes in build list, or be an addressable loaded at some point prior to connecting or while connected to a session. + +## Client Synchronization and Scene Validation +By combining these two scene management features, you can preload scenes: +- That should not be synchronized and/or are specific to the server or client side of a network session. +- That you know will be synchronized in order to reduce the client synchronization process. + +This approach can be useful if you: +- Want to have server-side only content that is loaded only on a server. +- Want to load local scenes for UI purposes or content that you don't need to synchronize. + - *This example separates the colliders of the scene geometry from the mesh filters and renderers. Clients are the only instances that load the graphics related scene assets. Both clients and server load a shared scene that contains in-scene placed NetworkObjects and the colliders for the "floor"* + +[Read the documentation for more information about client synchronization mode.](https://docs-multiplayer.unity3d.com/netcode/current/basics/scenemanagement/client-synchronization-mode/) + +### Note on Distributed Authority +Since a distributed authority network topology is designed to keep all clients synchronized enough with the SDK level states in order to allow for rapid session owner promotion, the most common design approach (relative to this example) is to have local client scenes and the scenes to be synchronized between clients. However, there could be edge case scenarios where perhaps you only want there to only ever be a single session owner and upon the session owner disconnecting all clients will disconnect themselves too. Under this type of design, it would be valid to have local "server-side" (i.e. session owner) scenes. This example implements this by default for example purposes only. If you want your project to use session owner promotion, then it is highly recommended to only use the have client-side local and shared scenes approach. + +## The Bootstrap Loading Process +![image](Images/ScenesView.png) + +### BootstrapScene +The first scene loaded. Contains a `NetworkManagerBootstrapper` in-scene placed `GameObject`. +![image](Images/NetworkManagerBootstrapper.png) + +#### NetworkManager Bootstrapper (component) +![image](Images/NetworkManagerBootstrapperProperties.png) + +Handles the pre-network session menu interface along with connect and disconnect events. Since it is derived from `NetworkManager`, it also defines the network session configuration (i.e. `NetworkConfig`). The `NetworkManagerBootstrapper` in-scene placed `GameObject` gets loaded into the DDOL scene automatically and will persist throughout the application life time. This derived class requires the `SceneBootstrapLoader` component. + +#### Scene Bootstrap Loader (component) +![image](Images/SceneBootstrapLoader.png) + +This component handles preloading scenes for both the client(s) and server. Upon being started, the `NetworkManagerBootstrapper` component will invoke `SceneBootstrapLoader.LoadMainMenu` method that kicks off the scene preloading process. +- **Default Active Scene Asset:** There is always an active scene. For this example, the default active scene is the same on both the client and server relative properties. *The active scene is always (and should always) be a "shared scene".* + - This could represent a lobby or network session main menu (i.e. create or join session). + - Both the client and the server preload this scene prior to starting a network session. +- **Local Scene Assets:** There could be times where you want to load scenes specific to the `NetworkManager` instance type (i.e. client, host, or server). + - These scenes are not synchronized by a server (client-server) or session owner (distributed authority). + - Having different locally loaded scenes is typically more common in a client-server network topology. + - In a distributed authority network topology, it is more common to keep all scenes synchronized but you might want to load non-synchronized scenes (i.e. menu interface for settings etc). +- **Shared Scene Assets:** These scenes are synchronized by the server or session owner (depending upon network topology used). + - This example only provides a server specific set of scene assets to load because you can always add those same scenes to the client-side locally loaded scenes. + - If the server synchronizes any scenes from the share scene assets with a client that already has those scene loaded, then those locally loaded scenes on the client side will be used during synchronization. + - Depending upon how many scenes you want to synchronize and/or how large one or more scenes are, preloading scenes can reduce synchronization time for clients. +The `NetworkManagerBootstrapper` uses the `SceneBootstrapLoader` component to start the creation or joining of a network session. The logical flow looks like: +- `NetworkManagerBootstrapper` invokes `SceneBootstrapLoader.StartSession` when you click one of the (very simple) main menu buttons and passes in the mode/type of `NetworkManager` to start. +- Based on the `NetworkManager` type being started, the `SceneBootstrapLoader` will then: + - Load the default active scene using the `UnityEngine.SceneManagement.SceneManager`. + - Load the local scenes using the `UnityEngine.SceneManagement.SceneManager`. + - Then it will create or join a network session by either starting the `NetworkManager` or connecting to the sesssion via multiplayer services. + - _Server or Session Owner only:_ + - If any, load the shared (i.e. synchronized) scene assets using the `NetworkSceneManager` + +#### Network Prefab Override Handler (component) +![image](Images/NetworkPrefabOverrideHandler.png) + +This `MonoBehaviour` component implements the `INetworkPrefabInstanceHandler` interface and registers with the `NetworkManafer.PrefabHandler` when started. It contains two network prefab properties: +- Network Prefab: This is the network prefab that you want to override. In this example, it is what is used to spawn a server-side player prefab and is what is defined within the `NetworkManagerBootstrapper` component. +- Network Prefab Override: This is what is used to spawn a player prefab on the client-side. + +At runtime the local `NetworkManager` instance is a client/host or server and will spawn either the ClientPlayer or ServerPlayer prefab. The `NetworkPrefabOverrideHandler` does not need to be a `NetworkBehaviour` and sometimes (especially for overriding the player prefab) it is better to handle prefab handlers prior to starting the `NetworkManager`. + +## Input Controls +The following is a list of the input controls used in this project: +### Clients Only +- `W,A,S,D`: Movement (forward, backward, turn left, turn right) + - *Arrow keys can be used as well.* +- `SPACE`: Jumps +- `P`: Enable disable player name and information. +- `C`: Toggles the player child object movement (continuous or only when the player moves). + +### Server Only +- `[,]`: Right and left bracket keys will follow spawned players. +- `Backspace`: Stop following a player. + +## Example Limitations +This example is primarily to provide a starting point for anyone interested in exploring how to override (customize) the scene loading and/or prefab instantiation. It does not cover all possible use case scenarios. It is recommended to explore this example, modify it, and read the [Netcode for GameObjects documentation](https://docs-multiplayer.unity3d.com/netcode/current/about/) for more details. + + + + + + From c0e65f46cfe65a90b805351d596152dd273471a6 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 15 Dec 2024 18:23:50 -0600 Subject: [PATCH 153/236] fix NetworkAnimator started throwing this exception: "Objects are trying to be loaded during a domain backup. This is not allowed as it will lead to undefined behaviour!" When MPPM was being used. This has to do with the OnBeforeSerialize method and potentially an order of operations issue. We can accomplish the same serialization within OnValidate, but went ahead and made it virtual in order to provide users with the ability to override and handle their own validation. --- .../Runtime/Components/NetworkAnimator.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index f6f40ea2b7..583e7bda4a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -298,14 +298,11 @@ private void ParseStateMachineStates(int layerIndex, ref AnimatorController anim } } -#endif - ///

/// Creates the TransitionStateInfoList table /// private void BuildTransitionStateInfoList() { -#if UNITY_EDITOR if (m_Animator == null) { return; @@ -323,9 +320,18 @@ private void BuildTransitionStateInfoList() var stateMachine = animatorController.layers[x].stateMachine; ParseStateMachineStates(x, ref animatorController, ref stateMachine); } -#endif } + /// + /// In-Editor Only + /// Virtual OnValidate method for custom derived NetworkAnimator classes. + /// + protected virtual void OnValidate() + { + BuildTransitionStateInfoList(); + } +#endif + public void OnAfterDeserialize() { BuildDestinationToTransitionInfoTable(); @@ -333,7 +339,7 @@ public void OnAfterDeserialize() public void OnBeforeSerialize() { - BuildTransitionStateInfoList(); + // Do nothing when serializing (handled during OnValidate) } internal struct AnimationState : INetworkSerializable From ba490651a8c6cf2f98c2bbacef8b24a1170860b3 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 16 Dec 2024 00:38:19 -0600 Subject: [PATCH 154/236] fix: server, host, or session owner not populating in-scene placed object table when started (#3177) * fix This fixes the issue with the server, host, or session owner not adding in-scene placed NetworkObjects to its internal table if the scene was loaded prior to starting the NetworkManager. * update Adding change log entry * update Adding PR# to this entry. * style remove whitespace --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Spawning/NetworkSpawnManager.cs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ff93d501ee..e6930d1077 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -17,6 +17,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where the server, host, or session owner would not populate the in-scene place `NetworkObject` table if the scene was loaded prior to starting the `NetworkManager`. (#3177) - Fixed issue where the `NetworkObjectIdHash` value could be incorrect when entering play mode while still in prefab edit mode with pending changes and using MPPM. (#3162) - Fixed issue where a sever only `NetworkManager` instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160) - Fixed issue where only the session owner (as opposed to all clients) would handle spawning prefab overrides properly when using a distributed authority network topology. (#3160) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index ed0b0348b7..854bc8fba2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1449,6 +1449,15 @@ internal void ServerSpawnSceneObjectsOnStartSweep() } } + // Since we are spawing in-scene placed NetworkObjects for already loaded scenes, + // we need to add any in-scene placed NetworkObject to our tracking table + var clearFirst = true; + foreach (var sceneLoaded in NetworkManager.SceneManager.ScenesLoaded) + { + NetworkManager.SceneManager.PopulateScenePlacedObjects(sceneLoaded.Value, clearFirst); + clearFirst = false; + } + // Notify all in-scene placed NetworkObjects have been spawned foreach (var networkObject in networkObjectsToSpawn) { From d7ef262c18eeba0157d26b2c5b785238ba2e681c Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 16 Dec 2024 10:14:22 -0600 Subject: [PATCH 155/236] update Updating Readme to reflect the version of Unity support by NGO v2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cb6984e3b..a45f6909a0 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ For general questions, networking advice or discussions about Netcode for GameOb ### Compatibility Netcode for GameObjects targets the following Unity versions: -- Unity 2021.3(LTS), and 2022.3(LTS) +- Unity 6 (LTS) On the following runtime platforms: - Windows, MacOS, and Linux From 628868a8bde86c27a21367e5ad35a0e9dc5a6142 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 16 Dec 2024 10:51:58 -0600 Subject: [PATCH 156/236] Update Minor grammar update --- Examples/OverridingScenesAndPrefabs/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/OverridingScenesAndPrefabs/Readme.md b/Examples/OverridingScenesAndPrefabs/Readme.md index 478c38d5a8..f411aae755 100644 --- a/Examples/OverridingScenesAndPrefabs/Readme.md +++ b/Examples/OverridingScenesAndPrefabs/Readme.md @@ -87,7 +87,7 @@ This `MonoBehaviour` component implements the `INetworkPrefabInstanceHandler` in - Network Prefab: This is the network prefab that you want to override. In this example, it is what is used to spawn a server-side player prefab and is what is defined within the `NetworkManagerBootstrapper` component. - Network Prefab Override: This is what is used to spawn a player prefab on the client-side. -At runtime the local `NetworkManager` instance is a client/host or server and will spawn either the ClientPlayer or ServerPlayer prefab. The `NetworkPrefabOverrideHandler` does not need to be a `NetworkBehaviour` and sometimes (especially for overriding the player prefab) it is better to handle prefab handlers prior to starting the `NetworkManager`. +At runtime the local `NetworkManager` instance is a client/host or server and will spawn either the ClientPlayer or ServerPlayer prefab. The `NetworkPrefabOverrideHandler` does not need to be a `NetworkBehaviour` and sometimes (especially for overriding the player prefab) it is better to register prefab handlers prior to starting the `NetworkManager`. ## Input Controls The following is a list of the input controls used in this project: From ba35384b09e132263e56ee799ee63ec1b8c0ae2a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 16 Dec 2024 14:50:21 -0600 Subject: [PATCH 157/236] chore: merge release 2.2.0 back into develop-2.0.0 (#3179) * update updating package * update updating changelog * update moving minimum editor back down to 6000.0 * fix Fixing the changelog version goof of mine. Making the license copyright just a year and not year range. * style removing whitespace from copyright notice. adding dash to changelog header * style Removing whitespaces for PVP-124-2 issues. * style missed one whitespace... * chore: merge develop 2 0 0 updates with SessionOwner permissions (#3176) * fix: clamp spawntimeout to recommended range (#3174) * update Clamping spawntimeout * update improving parenting failed message when either the child or parent NetworkObject is not spawnd. * update Update the local SceneEventData.SceneEventType on the authority side for SceneLoadComplete. * style removing whitespaces * feat: Add a SessionOwner ObjectStatus and allow InScenePlaced network objects to be distributed (#3175) * Initial pass on SessionOwner ownership flag * feat: Add SessionOwner OwnershipStatus flag * update removing additional session owner accessor. * Add OwnershipPermissions tests * fix client connect * Revert "fix client connect" This reverts commit 3c3b35444200da308dd07e1dd2e7b591fa0842d5. * update object distribution for in-scene placed NetworkObjects needs to use the InScenePlacedSourceGlobalObjectIdHash when building an object distribution list. * Add changelog * Remove unnecessary change * Remove Settings.json * Reword CHANGELOG * fix DAHost should not distribute session owner permission NetworkObjects. When client is promoted to session owner, for now newly promoted client takes ownership of NetworkObjects that have the SessionOwner permission set. Only prevent non-session owners from taking ownership of a NetworkObject with the SessionOwner permission set. * test fix Avoid the RemoveOwnership client-server only method. * style Visual studio code cleanup likes to sort by alpha... fixing for our standards. * update Adding check for session owner trying to change ownership to a non-session owner client. * test Adding an additional validation that a non-session owner cannot change ownership and that a session owner cannot change ownership to a non-session owner when the NetworkObject in question has the SessionOwner permissions set. --------- Co-authored-by: NoelStephensUnity --------- Co-authored-by: Emma * fix NetworkAnimator started throwing this exception: "Objects are trying to be loaded during a domain backup. This is not allowed as it will lead to undefined behaviour!" When MPPM was being used. This has to do with the OnBeforeSerialize method and potentially an order of operations issue. We can accomplish the same serialization within OnValidate, but went ahead and made it virtual in order to provide users with the ability to override and handle their own validation. * fix: server, host, or session owner not populating in-scene placed object table when started (#3177) * fix This fixes the issue with the server, host, or session owner not adding in-scene placed NetworkObjects to its internal table if the scene was loaded prior to starting the NetworkManager. * update Adding change log entry * update Adding PR# to this entry. * style remove whitespace * update adding unreleased change log --------- Co-authored-by: Emma --- com.unity.netcode.gameobjects/CHANGELOG.md | 20 ++- .../Editor/NetworkManagerEditor.cs | 2 +- .../Editor/NetworkObjectEditor.cs | 4 +- com.unity.netcode.gameobjects/LICENSE.md | 4 +- .../Runtime/Components/NetworkAnimator.cs | 49 +++++--- .../Components/NetworkRigidBodyBase.cs | 44 +++---- .../Runtime/Components/NetworkTransform.cs | 116 +++++++++--------- .../RigidbodyContactEventManager.cs | 10 +- .../Runtime/Core/NetworkObjectRefreshTool.cs | 2 +- .../Runtime/Messaging/ILPPMessageProvider.cs | 2 +- .../Messages/ChangeOwnershipMessage.cs | 10 +- .../Messaging/Messages/CreateObjectMessage.cs | 2 +- .../Messages/NetworkTransformMessage.cs | 2 +- .../Messaging/RpcTargets/ServerRpcTarget.cs | 2 +- .../Runtime/Serialization/BitWriter.cs | 2 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 9 ++ .../Bootstrap/Scripts/Bootstrap.asmdef | 4 +- .../NetworkTransformOwnershipTests.cs | 2 +- .../NetworkTransform/NetworkTransformTests.cs | 2 +- .../NetworkVariableCollectionsTests.cs | 12 +- .../Prefabs/NetworkPrefabOverrideTests.cs | 4 +- com.unity.netcode.gameobjects/package.json | 4 +- 22 files changed, 169 insertions(+), 139 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d253135563..423096ac35 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,7 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -[Unreleased] +## [Unreleased] + +### Added + +### Fixed + +### Changed + + +## [2.2.0] - 2024-12-12 ### Added @@ -16,6 +25,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where the server, host, or session owner would not populate the in-scene place `NetworkObject` table if the scene was loaded prior to starting the `NetworkManager`. (#3177) - Fixed issue where the `NetworkObjectIdHash` value could be incorrect when entering play mode while still in prefab edit mode with pending changes and using MPPM. (#3162) - Fixed issue where a sever only `NetworkManager` instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160) - Fixed issue where only the session owner (as opposed to all clients) would handle spawning prefab overrides properly when using a distributed authority network topology. (#3160) @@ -216,8 +226,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [2.0.0-exp.2] - 2024-04-02 ### Added -- Added updates to all internal messages to account for a distributed authority network session connection. (#2863) -- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) +- Added updates to all internal messages to account for a distributed authority network session connection. (#2863) +- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) - For a customized `NetworkRigidbodyBase` class: - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes. - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned. @@ -353,6 +363,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where you could not have multiple source network prefab overrides targeting the same network prefab as their override. (#2710) ### Changed + - Changed the server or host shutdown so it will now perform a "soft shutdown" when `NetworkManager.Shutdown` is invoked. This will send a disconnect notification to all connected clients and the server-host will wait for all connected clients to disconnect or timeout after a 5 second period before completing the shutdown process. (#2789) - Changed `OnClientDisconnectedCallback` will now return the assigned client identifier on the local client side if the client was approved and assigned one prior to being disconnected. (#2789) - Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777) @@ -365,6 +376,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Changed in-scene placed `NetworkObject`s now set their `IsSceneObject` value when generating their `GlobalObjectIdHash` value. (#2710) - Changed the default `NetworkConfig.SpawnTimeout` value from 1.0s to 10.0s. (#2710) + ## [1.7.1] - 2023-11-15 ### Added @@ -414,7 +426,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added a protected virtual method `NetworkTransform.OnInitialize(ref NetworkTransformState replicatedState)` that just returns the replicated state reference. - + ### Fixed - Fixed issue where invoking `NetworkManager.Shutdown` within `NetworkManager.OnClientStopped` or `NetworkManager.OnServerStopped` would force `NetworkManager.ShutdownInProgress` to remain true after completing the shutdown process. (#2661) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs index 0684d74849..8ba4128b07 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs @@ -237,7 +237,7 @@ private void DisplayNetworkManagerProperties() if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) { EditorGUILayout.PropertyField(m_AutoSpawnPlayerPrefabClientSide, new GUIContent("Auto Spawn Player Prefab")); - } + } #endif EditorGUILayout.PropertyField(m_PlayerPrefabProperty, new GUIContent("Default Player Prefab")); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 25dd8967f1..0860fd9c92 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -198,8 +198,8 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten // The below can cause visual anomalies and/or throws an exception within the EditorGUI itself (index out of bounds of the array). and has // The visual anomaly is when you select one field it is set in the drop down but then the flags selection in the popup menu selects more items // even though if you exit the popup menu the flag setting is correct. - //var ownership = (NetworkObject.OwnershipStatus)EditorGUI.EnumFlagsField(position, label, (NetworkObject.OwnershipStatus)property.enumValueFlag); - //property.enumValueFlag = (int)ownership; + // var ownership = (NetworkObject.OwnershipStatus)EditorGUI.EnumFlagsField(position, label, (NetworkObject.OwnershipStatus)property.enumValueFlag); + // property.enumValueFlag = (int)ownership; EditorGUI.EndDisabledGroup(); EditorGUI.EndProperty(); } diff --git a/com.unity.netcode.gameobjects/LICENSE.md b/com.unity.netcode.gameobjects/LICENSE.md index 031978c204..ee8cecf4bf 100644 --- a/com.unity.netcode.gameobjects/LICENSE.md +++ b/com.unity.netcode.gameobjects/LICENSE.md @@ -1,7 +1,5 @@ Unity Companion License (UCL License) -com.unity.netcode.gameobjects copyright © 2021-2024 Unity Technologies +com.unity.netcode.gameobjects copyright © 2024 Unity Technologies Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license). Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. - - diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 7f2c4ae23a..583e7bda4a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -298,14 +298,11 @@ private void ParseStateMachineStates(int layerIndex, ref AnimatorController anim } } -#endif - /// /// Creates the TransitionStateInfoList table /// private void BuildTransitionStateInfoList() { -#if UNITY_EDITOR if (m_Animator == null) { return; @@ -323,9 +320,18 @@ private void BuildTransitionStateInfoList() var stateMachine = animatorController.layers[x].stateMachine; ParseStateMachineStates(x, ref animatorController, ref stateMachine); } -#endif } + /// + /// In-Editor Only + /// Virtual OnValidate method for custom derived NetworkAnimator classes. + /// + protected virtual void OnValidate() + { + BuildTransitionStateInfoList(); + } +#endif + public void OnAfterDeserialize() { BuildDestinationToTransitionInfoTable(); @@ -333,7 +339,7 @@ public void OnAfterDeserialize() public void OnBeforeSerialize() { - BuildTransitionStateInfoList(); + // Do nothing when serializing (handled during OnValidate) } internal struct AnimationState : INetworkSerializable @@ -416,8 +422,8 @@ internal struct AnimationMessage : INetworkSerializable internal bool HasBeenProcessed; // This is preallocated/populated in OnNetworkSpawn for all instances in the event ownership or - // authority changes. When serializing, IsDirtyCount determines how many AnimationState entries - // should be serialized from the list. When deserializing the list is created and populated with + // authority changes. When serializing, IsDirtyCount determines how many AnimationState entries + // should be serialized from the list. When deserializing the list is created and populated with // only the number of AnimationStates received which is dictated by the deserialized IsDirtyCount. internal List AnimationStates; @@ -493,7 +499,7 @@ internal bool IsServerAuthoritative() } /// - /// Override this method and return false to switch to owner authoritative mode + /// Override this method and return false to switch to owner authoritative mode. /// /// /// When using a distributed authority network topology, this will default to @@ -731,7 +737,7 @@ public override void OnNetworkDespawn() } /// - /// Wries all parameter and state information needed to initially synchronize a client + /// Writes all parameter and state information needed to initially synchronize a client /// private void WriteSynchronizationData(ref BufferSerializer serializer) where T : IReaderWriter { @@ -806,8 +812,10 @@ private void WriteSynchronizationData(ref BufferSerializer serializer) whe } } - animationState.Transition = isInTransition; // The only time this could be set to true - animationState.StateHash = stateHash; // When a transition, this is the originating/starting state + // The only time this could be set to true + animationState.Transition = isInTransition; + // When a transition, this is the originating/starting state + animationState.StateHash = stateHash; animationState.NormalizedTime = normalizedTime; animationState.Layer = layer; animationState.Weight = m_LayerWeights[layer]; @@ -881,7 +889,8 @@ private void CheckForStateChange(int layer) { m_TransitionHash[layer] = nt.fullPathHash; m_AnimationHash[layer] = 0; - animState.DestinationStateHash = nt.fullPathHash; // Next state is the destination state for cross fade + // Next state is the destination state for cross fade + animState.DestinationStateHash = nt.fullPathHash; animState.CrossFade = true; animState.Transition = true; animState.Duration = tt.duration; @@ -899,7 +908,8 @@ private void CheckForStateChange(int layer) // first time in this transition for this layer m_TransitionHash[layer] = tt.fullPathHash; m_AnimationHash[layer] = 0; - animState.StateHash = tt.fullPathHash; // Transitioning from state + // Transitioning from state + animState.StateHash = tt.fullPathHash; animState.CrossFade = false; animState.Transition = true; animState.NormalizedTime = tt.normalizedTime; @@ -1115,7 +1125,7 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) { writer.Seek(0); writer.Truncate(); - // Write how many parameter entries we are going to write + // Write out how many parameter entries to read BytePacker.WriteValuePacked(writer, (uint)m_ParametersToUpdate.Count); foreach (var parameterIndex in m_ParametersToUpdate) { @@ -1264,7 +1274,7 @@ internal void UpdateAnimationState(AnimationState animationState) NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) sub-table does not contain destination state ({animationState.DestinationStateHash})!"); } } - // For reference, it is valid to have no transition information + // For reference, it is valid to have no transition information //else if (NetworkManager.LogLevel == LogLevel.Developer) //{ // NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!"); @@ -1471,7 +1481,7 @@ private void InternalSetTrigger(int hash, bool isSet = true) /// /// Distributed Authority: Internally-called RPC client receiving function to update a trigger when the server wants to forward - /// a trigger for a client to play / reset + /// a trigger to a client /// /// the payload containing the trigger data to apply [Rpc(SendTo.NotAuthority)] @@ -1482,7 +1492,7 @@ internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage /// /// Client Server: Internally-called RPC client receiving function to update a trigger when the server wants to forward - /// a trigger for a client to play / reset + /// a trigger to a client /// /// the payload containing the trigger data to apply /// unused @@ -1548,7 +1558,7 @@ public void SetTrigger(int hash, bool setTrigger = true) } /// - /// Resets the trigger for the associated animation. See SetTrigger for more on how triggers are special + /// Resets the trigger for the associated animation. See SetTrigger for more on how triggers are special /// /// The string name of the trigger to reset public void ResetTrigger(string triggerName) @@ -1564,4 +1574,5 @@ public void ResetTrigger(int hash) } } } -#endif // COM_UNITY_MODULES_ANIMATION +// COM_UNITY_MODULES_ANIMATION +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 7e8808171a..18b393bec0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -180,7 +180,7 @@ internal Vector3 GetAdjustedRotationThreshold() ///

$xV_Q%ka%T+>5h9B^llW;|Xo`_aRDBOFix1z7) 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

/// /// For , only the x and y components of the are applied. - /// + /// public void SetLinearVelocity(Vector3 linearVelocity) { if (m_IsRigidbody2D) @@ -546,7 +546,7 @@ public void SetIsKinematic(bool isKinematic) { if (IsKinematic()) { - // If not already set to interpolate then set the Rigidbody to interpolate + // If not already set to interpolate then set the Rigidbody to interpolate if (m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate) { // Sleep until the next fixed update when switching from extrapolation to interpolation @@ -849,36 +849,30 @@ public void DetachFromFixedJoint() } if (UseRigidBodyForMotion) { - if (m_IsRigidbody2D) + if (m_IsRigidbody2D && FixedJoint2D != null) { - if (FixedJoint2D != null) + if (!m_FixedJoint2DUsingGravity) { - if (!m_FixedJoint2DUsingGravity) - { - FixedJoint2D.connectedBody.gravityScale = m_OriginalGravityScale; - } - FixedJoint2D.connectedBody = null; - Destroy(FixedJoint2D); - FixedJoint2D = null; - ResetInterpolation(); - RemoveFromParentBody(); + FixedJoint2D.connectedBody.gravityScale = m_OriginalGravityScale; } + FixedJoint2D.connectedBody = null; + Destroy(FixedJoint2D); + FixedJoint2D = null; + ResetInterpolation(); + RemoveFromParentBody(); } - else + else if (FixedJoint != null) { - if (FixedJoint != null) - { - FixedJoint.connectedBody = null; - m_InternalRigidbody.useGravity = m_OriginalGravitySetting; - Destroy(FixedJoint); - FixedJoint = null; - ResetInterpolation(); - RemoveFromParentBody(); - } + FixedJoint.connectedBody = null; + m_InternalRigidbody.useGravity = m_OriginalGravitySetting; + Destroy(FixedJoint); + FixedJoint = null; + ResetInterpolation(); + RemoveFromParentBody(); } } } } } -#endif // COM_UNITY_MODULES_PHYSICS - +// COM_UNITY_MODULES_PHYSICS +#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index a103b0fc4b..3d2be47c63 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -32,7 +32,8 @@ public class NetworkTransform : NetworkBehaviour ///
public struct NetworkTransformState : INetworkSerializable { - private const int k_InLocalSpaceBit = 0x00000001; // Persists between state updates (authority dictates if this is set) + // Persists between state updates (authority dictates if this is set) + private const int k_InLocalSpaceBit = 0x00000001; private const int k_PositionXBit = 0x00000002; private const int k_PositionYBit = 0x00000004; private const int k_PositionZBit = 0x00000008; @@ -43,18 +44,25 @@ public struct NetworkTransformState : INetworkSerializable private const int k_ScaleYBit = 0x00000100; private const int k_ScaleZBit = 0x00000200; private const int k_TeleportingBit = 0x00000400; - private const int k_Interpolate = 0x00000800; // Persists between state updates (authority dictates if this is set) - private const int k_QuaternionSync = 0x00001000; // Persists between state updates (authority dictates if this is set) - private const int k_QuaternionCompress = 0x00002000; // Persists between state updates (authority dictates if this is set) - private const int k_UseHalfFloats = 0x00004000; // Persists between state updates (authority dictates if this is set) + // Persists between state updates (authority dictates if this is set) + private const int k_Interpolate = 0x00000800; + // Persists between state updates (authority dictates if this is set) + private const int k_QuaternionSync = 0x00001000; + // Persists between state updates (authority dictates if this is set) + private const int k_QuaternionCompress = 0x00002000; + // Persists between state updates (authority dictates if this is set) + private const int k_UseHalfFloats = 0x00004000; private const int k_Synchronization = 0x00008000; - private const int k_PositionSlerp = 0x00010000; // Persists between state updates (authority dictates if this is set) - private const int k_IsParented = 0x00020000; // When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order + // Persists between state updates (authority dictates if this is set) + private const int k_PositionSlerp = 0x00010000; + // When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order + private const int k_IsParented = 0x00020000; private const int k_SynchBaseHalfFloat = 0x00040000; private const int k_ReliableSequenced = 0x00080000; private const int k_UseUnreliableDeltas = 0x00100000; private const int k_UnreliableFrameSync = 0x00200000; - private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier + // (Internal Debugging) When set each state update will contain a state identifier + private const int k_TrackStateId = 0x10000000; // Stores persistent and state relative flags private uint m_Bitset; @@ -409,8 +417,8 @@ internal set } /// - /// Returns whether this state update was a frame synchronization when - /// UseUnreliableDeltas is enabled. When set, the entire transform will + /// Returns whether this state update was a frame synchronization when + /// UseUnreliableDeltas is enabled. When set, the entire transform will /// be or has been synchronized. /// public bool IsUnreliableFrameSync() @@ -929,8 +937,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endregion #region PROPERTIES AND GENERAL METHODS - - public enum AuthorityModes { Server, @@ -1370,7 +1376,8 @@ internal NetworkTransformState LocalAuthoritativeNetworkState private BufferedLinearInterpolatorVector3 m_PositionInterpolator; private BufferedLinearInterpolatorVector3 m_ScaleInterpolator; - private BufferedLinearInterpolatorQuaternion m_RotationInterpolator; // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value + private BufferedLinearInterpolatorQuaternion m_RotationInterpolator; // The previous network state private NetworkTransformState m_OldState = new NetworkTransformState(); @@ -1643,11 +1650,11 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz Debug.LogException(ex); } - // The below is part of assuring we only send a frame synch, when sending unreliable deltas, if + // The below is part of assuring we only send a frame synch, when sending unreliable deltas, if // we have already sent at least one unreliable delta state update. At this point in the callstack, // a delta state update has just been sent in the above UpdateTransformState() call and as long as // we didn't send a frame synch and we are not synchronizing then we know at least one unreliable - // delta has been sent. Under this scenario, we should start checking for this instance's alloted + // delta has been sent. Under this scenario, we should start checking for this instance's alloted // frame synch "tick slot". Once we send a frame synch, if no other deltas occur after that // (i.e. the object is at rest) then we will stop sending frame synch's until the object begins // moving, rotating, or scaling again. @@ -1964,7 +1971,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra networkState.NetworkDeltaPosition = m_HalfPositionState; - // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position + // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position if ((m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick || isAxisSync) && !networkState.IsTeleportingNextFrame) { networkState.SynchronizeBaseHalfFloat = true; @@ -3403,7 +3410,7 @@ internal void ChildRegistration(NetworkObject child, bool isAdding) /// - Local space to local space ( parent to parent) /// Will all smoothly transition while interpolation is enabled. /// (Does not work if using a or for motion) - /// + /// /// When a parent changes, non-authoritative instances should:
/// - Apply the resultant position, rotation, and scale from the parenting action.
/// - Clear interpolators (even if not enabled on this frame)
@@ -3575,7 +3582,7 @@ private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool s var transformToCommit = transform; - // Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will + // Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will // update the axial deltas to whatever changes are applied). As such, we need to preserve the dirty and explicit // state flags. var stateWasDirty = m_LocalAuthoritativeNetworkState.IsDirty; @@ -3658,7 +3665,7 @@ private void UpdateInterpolation() var serverTime = m_CachedNetworkManager.ServerTime; var cachedServerTime = serverTime.Time; - //var offset = (float)serverTime.TickOffset; + // var offset = (float)serverTime.TickOffset; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else @@ -3669,7 +3676,7 @@ private void UpdateInterpolation() // is to make their cachedRenderTime run 2 ticks behind. // TODO: This could most likely just always be 2 - //var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; + // var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; var ticksAgo = 2; var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; @@ -3746,7 +3753,7 @@ public virtual void OnFixedUpdate() /// /// Determines whether the is or based on the property. - /// You can override this method to control this logic. + /// You can override this method to control this logic. /// /// or protected virtual bool OnIsServerAuthoritative() @@ -3772,7 +3779,6 @@ public bool IsServerAuthoritative() return OnIsServerAuthoritative(); } } - #endregion #region MESSAGE HANDLING @@ -3964,7 +3970,7 @@ internal float TicksAgoInSeconds() { return 2 * m_TickFrequency; // TODO: We need an RTT that updates regularly and not just when the client sends packets - //return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; + // return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; } /// @@ -3974,25 +3980,25 @@ internal float TicksAgoInSeconds() private void TickUpdate() { // TODO: We need an RTT that updates regularly and not just when the client sends packets - //if (m_UnityTransport != null) - //{ - // // Determine the desired ticks ago by the RTT (this really should be the combination of the - // // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor - // // network quality so latent interpolation is going to be expected). - // var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); - // m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); - // var tickAgoSum = 0.0f; - // foreach (var tickAgo in m_TicksAgoSamples) - // { - // tickAgoSum += tickAgo; - // } - // m_PreviousTicksAgo = TicksAgo; - // TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); - // m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; - // // Get the partial tick value for when this is all calculated to provide an offset for determining - // // the relative starting interpolation point for the next update - // Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); - //} + // if (m_UnityTransport != null) + // { + // // Determine the desired ticks ago by the RTT (this really should be the combination of the + // // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor + // // network quality so latent interpolation is going to be expected). + // var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); + // m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); + // var tickAgoSum = 0.0f; + // foreach (var tickAgo in m_TicksAgoSamples) + // { + // tickAgoSum += tickAgo; + // } + // m_PreviousTicksAgo = TicksAgo; + // TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); + // m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; + // // Get the partial tick value for when this is all calculated to provide an offset for determining + // // the relative starting interpolation point for the next update + // Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); + // } // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before if (m_NetworkManager.ServerTime.Tick <= m_LastTick) @@ -4012,13 +4018,13 @@ private void TickUpdate() private UnityTransport m_UnityTransport; private float m_TickFrequency; - //private float m_OffsetTickFrequency; - //private ulong m_TickInMS; - //private int m_TickSampleIndex; + // private float m_OffsetTickFrequency; + // private ulong m_TickInMS; + // private int m_TickSampleIndex; private int m_TickRate; public float TicksAgo { get; private set; } - //public float Offset { get; private set; } - //private float m_PreviousTicksAgo; + // public float Offset { get; private set; } + // private float m_PreviousTicksAgo; private List m_TicksAgoSamples = new List(); @@ -4032,16 +4038,16 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) //// For the offset, it uses the fractional remainder of the tick to determine the offset. //// In order to keep within tick boundaries, we increment the tick rate by 1 to assure it //// will always be < the tick frequency. - //m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); - //m_TickInMS = (ulong)(1000 * m_TickFrequency); - //m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; + // m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); + // m_TickInMS = (ulong)(1000 * m_TickFrequency); + // m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; //// Fill the sample with a starting value of 1 - //for (int i = 0; i < m_TickRate; i++) - //{ - // m_TicksAgoSamples.Add(1f); - //} + // for (int i = 0; i < m_TickRate; i++) + // { + // m_TicksAgoSamples.Add(1f); + // } TicksAgo = 2f; - //m_PreviousTicksAgo = 1f; + // m_PreviousTicksAgo = 1f; if (networkManager.IsServer) { networkManager.OnServerStopped += OnNetworkManagerStopped; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index d0808d2886..02f9c98e7e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -27,7 +27,7 @@ public struct ContactEventHandlerInfo } /// - /// Default implementation required to register a with a instance. + /// Default implementation required to register a with a instance. /// /// /// Recommended to implement this method on a component @@ -52,7 +52,7 @@ public interface IContactEventHandler } /// - /// This is an extended version of and can be used to register a with a instance.
+ /// This is an extended version of and can be used to register a with a instance.
/// This provides additional information to the for each set of contact events it is processing. ///
public interface IContactEventHandlerWithInfo : IContactEventHandler @@ -66,9 +66,9 @@ public interface IContactEventHandlerWithInfo : IContactEventHandler /// /// Add this component to an in-scene placed GameObject to provide faster collision event processing between instances and optionally static colliders. - ///
- ///
- ///
+ ///
+ ///
+ ///
///
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")] public class RigidbodyContactEventManager : MonoBehaviour diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs index 63d48e914d..5309377475 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObjectRefreshTool.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode { /// - /// This is a helper tool to update all in-scene placed instances of a prefab that + /// This is a helper tool to update all in-scene placed instances of a prefab that /// originally did not have a NetworkObject component but one was added to the prefab /// later. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index b7f90f658f..4e697acc0d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -16,7 +16,7 @@ internal struct ILPPMessageProvider : INetworkMessageProvider /// /// Enum representing the different types of messages that can be sent over the network. /// The values cannot be changed, as they are used to serialize and deserialize messages. - /// Adding new messages should be done by adding new values to the end of the enum + /// Adding new messages should be done by adding new values to the end of the enum /// using the next free value. /// /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index cb4f114b91..998e84d640 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -17,7 +17,7 @@ internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMem /// /// When requesting, RequestClientId is the requestor. /// When approving, RequestClientId is the owner that approved. - /// When responding (only for denied), RequestClientId is the requestor + /// When responding (only for denied), RequestClientId is the requestor /// internal ulong RequestClientId; internal int ClientIdCount; @@ -272,7 +272,7 @@ public void Handle(ref NetworkContext context) networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, clientId); } } - // If the NetworkObject is not visible to the DAHost client, then exit early + // If the NetworkObject is not visible to the DAHost client, then exit early if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { return; @@ -294,7 +294,7 @@ public void Handle(ref NetworkContext context) } /// - /// Handle the + /// Handle the extended distributed authority ownership updates /// /// private void HandleExtendedOwnershipUpdate(ref NetworkContext context) @@ -351,10 +351,10 @@ private void HandleOwnershipChange(ref NetworkContext context) networkObject.InvokeBehaviourOnLostOwnership(); } - // If in distributed authority mode + // If in distributed authority mode if (networkManager.DistributedAuthorityMode) { - // Always update the network properties in distributed authority mode + // Always update the network properties in distributed authority mode for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) { networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties(); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index 6c0656b90a..02f4263e9d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -257,7 +257,7 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende { // DA - NGO CMB SERVICE NOTES: // (*** See above notes fist ***) - // If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively + // If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively // the same frame), then we need to check the observers list to make sure all players are visible upon first spawning. At a later date, // for area of interest we will need to have some form of follow up "observer update" message to cull out players not within each // player's AOI. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs index 56f25f22c2..cf4013f469 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs @@ -128,7 +128,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } else if (networkManager.DAHost) { - // Specific to distributed authority mode, the only sender of state updates will be the owner + // Specific to distributed authority mode, the only sender of state updates will be the owner ownerClientId = context.SenderId; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 0db593f23b..87bfbfb1c4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -43,7 +43,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None); message.ReadBuffer = tempBuffer; message.Handle(ref context); - // If enabled, then add the RPC metrics for this + // If enabled, then add the RPC metrics for this #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE int length = tempBuffer.Length; if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs index 9eacd5601f..0e3ccfb7e3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs @@ -62,7 +62,7 @@ public void Dispose() /// When you know you will be writing multiple fields back-to-back and you know the total size, /// you can call TryBeginWriteBits() once on the total size, and then follow it with calls to /// WriteBit() or WriteBits(). - /// + /// /// Bitwise write operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginWriteBits(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index ed0b0348b7..854bc8fba2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1449,6 +1449,15 @@ internal void ServerSpawnSceneObjectsOnStartSweep() } } + // Since we are spawing in-scene placed NetworkObjects for already loaded scenes, + // we need to add any in-scene placed NetworkObject to our tracking table + var clearFirst = true; + foreach (var sceneLoaded in NetworkManager.SceneManager.ScenesLoaded) + { + NetworkManager.SceneManager.PopulateScenePlacedObjects(sceneLoaded.Value, clearFirst); + clearFirst = false; + } + // Notify all in-scene placed NetworkObjects have been spawned foreach (var networkObject in networkObjectsToSpawn) { diff --git a/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef index 07a8ac0096..54cb675609 100644 --- a/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef +++ b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/Bootstrap.asmdef @@ -1,7 +1,7 @@ { - "name": "Bootstrap", + "name": "Bootstrap", "rootNamespace": "Unity.Netcode.Samples", "references": [ "Unity.Netcode.Runtime" ] -} +} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index ca402c6095..229a067758 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -869,7 +869,7 @@ public IEnumerator NestedNetworkTransformSpawnPositionTest() foreach (var networkManager in m_NetworkManagers) { - // Randomize the position + // Randomize the position RandomizeObjectTransformPositions(m_SpawnObject); // Create an instance owned by the specified networkmanager diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 95ca7e0f20..7e12934a67 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -123,7 +123,7 @@ private void UpdateTransformWorld(Components.NetworkTransform networkTransformTe } /// - /// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies + /// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies /// [Test] public void SwitchTransformSpaceWhenParentedTest([Values(0.5f, 1.0f, 5.0f)] float scale) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs index 0c8a2ffdf9..3276b3d3df 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs @@ -20,7 +20,7 @@ namespace Unity.Netcode.RuntimeTests /// - HashSet /// This also does some testing on nested collections, but does /// not test every possible combination. - ///
+ ///
[TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] public class NetworkVariableCollectionsTests : NetcodeIntegrationTest @@ -218,7 +218,7 @@ public IEnumerator TestListBuiltInTypeCollections() yield return WaitForConditionOrTimeOut(() => compInt.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Client-{client.LocalClientId} full set failed to synchronize on {nameof(ListTestHelperInt)} {compInt.name}!"); ////////////////////////////////// - // Server Full Set + // Server Full Set compIntServer.FullSet(GetRandomIntList(5), ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compIntServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Server full set failed to synchronize on {nameof(ListTestHelperInt)} {compIntServer.name}!"); @@ -285,7 +285,7 @@ public IEnumerator TestListBuiltInTypeCollections() // Only test restore on non-host clients (otherwise a host is both server and client/owner) if (!client.IsServer) { - // No Write Client Remove List item with CheckDirtyState restore + // No Write Client Remove List item with CheckDirtyState restore compListInt.Remove(compListInt.ListCollectionServer.Value[index], ListTestHelperBase.Targets.Server); yield return WaitForConditionOrTimeOut(() => compListInt.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Client-{client.LocalClientId} remove failed to restore on {nameof(ListTestHelperListInt)} {compListIntServer.name}! {compListIntServer.GetLog()}"); @@ -474,7 +474,7 @@ public IEnumerator TestListSerializableObjectCollections() yield return WaitForConditionOrTimeOut(() => compObjectServer.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Client-{client.LocalClientId} change failed to restore on {nameof(ListTestHelperSerializableObject)} {compObjectServer.name}!"); - // No Write Client Remove Serializable item with owner state update restore + // No Write Client Remove Serializable item with owner state update restore compObject.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); } compObjectServer.ListCollectionServer.Value[index] = SerializableObject.GetRandomObject(); @@ -838,7 +838,7 @@ public IEnumerator TestDictionaryCollections() compDictionaryServer.ListCollectionOwner.IsDirty(); yield return WaitForConditionOrTimeOut(() => compDictionaryServer.CompareTrackedChanges(ListTestHelperBase.Targets.Owner)); AssertOnTimeout($"Server add to owner write collection property failed to restore on {className} {compDictionaryServer.name}! {compDictionaryServer.GetLog()}"); - // Server-side add the same key and SerializableObject to owner write permission (would throw key exists exception too if previous failed) + // Server-side add the same key and SerializableObject to owner write permission (would throw key exists exception too if previous failed) compDictionaryServer.ListCollectionOwner.Value.Add(newEntry.Item1, newEntry.Item2); // Server-side add a completely new key and SerializableObject to to owner write permission property compDictionaryServer.ListCollectionOwner.Value.Add(GetNextKey(), SerializableObject.GetRandomObject()); @@ -864,7 +864,7 @@ public IEnumerator TestDictionaryCollections() compDictionary.ListCollectionServer.IsDirty(); yield return WaitForConditionOrTimeOut(() => compDictionary.CompareTrackedChanges(ListTestHelperBase.Targets.Server)); AssertOnTimeout($"Client-{client.LocalClientId} add to server write collection property failed to restore on {className} {compDictionary.name}! {compDictionary.GetLog()}"); - // Client-side add the same key and SerializableObject to server write permission property (would throw key exists exception too if previous failed) + // Client-side add the same key and SerializableObject to server write permission property (would throw key exists exception too if previous failed) compDictionary.ListCollectionServer.Value.Add(newEntry.Item1, newEntry.Item2); // Client-side add a completely new key and SerializableObject to to server write permission property compDictionary.ListCollectionServer.Value.Add(GetNextKey(), SerializableObject.GetRandomObject()); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs index b7d86e3356..84d9c56fec 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs @@ -109,7 +109,7 @@ protected override void OnServerAndClientsCreated() playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab; playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab; - // Add the NetworkPrefab with override + // Add the NetworkPrefab with override m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride); // Add the client player prefab that will be used on clients (and the host) m_ServerNetworkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab); @@ -121,7 +121,7 @@ protected override void OnServerAndClientsCreated() playerPrefabOverrideHandler.ServerSideInstance = m_PlayerPrefab; playerPrefabOverrideHandler.ClientSideInstance = m_ClientSidePlayerPrefab.Prefab; - // Add the NetworkPrefab with override + // Add the NetworkPrefab with override networkManager.NetworkConfig.Prefabs.Add(m_PrefabOverride); // Add the client player prefab that will be used on clients (and the host) networkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab); diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index edbce0938e..c20e366c3c 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,11 +2,11 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.1.1", + "version": "2.2.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.3.0" + "com.unity.transport": "2.4.0" }, "samples": [ { From 53ec0e631ff749c234d80e5f6ead40f137996746 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 10 Jan 2025 15:48:32 -0600 Subject: [PATCH 158/236] fix: client owned NetworkObject with prefabhandler destroy order incorrect on host-server side (#3200) * fix Pass in false for the destroy object parameter when cleaning up objects owned by a client upon the client disconnecting and the spawned objects have a prefab handler registered for their associated network prefab. * update adding change log entry. * update Cleaning up the connection manager. Removing the warning about a client having nothing that it owns spawned (could be a valid design pattern and no reason to spam the message upon a client disconnecting if it is intentional). * test Updated NetworkPrefabOverrideTests to add a validation check for this PR * update Adding PR number to changelog entry. * style remove whitespaces * style remove unused namespace --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Connection/NetworkConnectionManager.cs | 157 +++++++++--------- .../Prefabs/NetworkPrefabOverrideTests.cs | 77 +++++++++ 3 files changed, 153 insertions(+), 83 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 423096ac35..c20a80df87 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) + ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index fe6bc4816d..06a07acfc4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1144,14 +1144,10 @@ internal void OnClientDisconnectFromServer(ulong clientId) if (NetworkManager.PrefabHandler.ContainsHandler(playerObject.GlobalObjectIdHash)) { - if (NetworkManager.DAHost && NetworkManager.DistributedAuthorityMode) - { - NetworkManager.SpawnManager.DespawnObject(playerObject, true, NetworkManager.DistributedAuthorityMode); - } - else - { - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(playerObject); - } + // 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) { @@ -1171,106 +1167,101 @@ internal void OnClientDisconnectFromServer(ulong clientId) // Get the NetworkObjects owned by the disconnected client var clientOwnedObjects = NetworkManager.SpawnManager.SpawnedObjectsList.Where((c) => c.OwnerClientId == clientId).ToList(); - if (clientOwnedObjects == null) - { - // This could happen if a client is never assigned a player object and is disconnected - // Only log this in verbose/developer mode - if (NetworkManager.LogLevel == LogLevel.Developer) - { - NetworkLog.LogWarning($"ClientID {clientId} disconnected with (0) zero owned objects! Was a player prefab not assigned?"); - } - } - else + + // Handle changing ownership and prefab handlers + var clientCounter = 0; + var predictedClientCount = ConnectedClientsList.Count - 1; + var remainingClients = NetworkManager.DistributedAuthorityMode ? ConnectedClientsList.Where((c) => c.ClientId != clientId).ToList() : null; + for (int i = clientOwnedObjects.Count - 1; i >= 0; i--) { - // Handle changing ownership and prefab handlers - var clientCounter = 0; - var predictedClientCount = ConnectedClientsList.Count - 1; - var remainingClients = NetworkManager.DistributedAuthorityMode ? ConnectedClientsList.Where((c) => c.ClientId != clientId).ToList() : null; - for (int i = clientOwnedObjects.Count - 1; i >= 0; i--) + var ownedObject = clientOwnedObjects[i]; + if (ownedObject != null) { - var ownedObject = clientOwnedObjects[i]; - if (ownedObject != null) + // If destroying with owner, then always despawn and destroy (or defer destroying to prefab handler) + if (!ownedObject.DontDestroyWithOwner) { - if (!ownedObject.DontDestroyWithOwner) + if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash)) { - if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash)) + if (ownedObject.IsSpawned) { - NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]); - } - else - { - NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); + // Don't destroy (prefab handler will determine this, but always notify + NetworkManager.SpawnManager.DespawnObject(ownedObject, false, true); } + NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]); } - else if (!NetworkManager.ShutdownInProgress) + else + { + NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); + } + } + else if (!NetworkManager.ShutdownInProgress) + { + // NOTE: All of the below code only handles ownership transfer. + // For client-server, we just remove the ownership. + // For distributed authority, we need to change ownership based on parenting + if (NetworkManager.DistributedAuthorityMode) { - // NOTE: All of the below code only handles ownership transfer. - // For client-server, we just remove the ownership. - // For distributed authority, we need to change ownership based on parenting - if (NetworkManager.DistributedAuthorityMode) + // Only NetworkObjects that have the OwnershipStatus.Distributable flag set and no parent + // (ownership is transferred to all children) will have their ownership redistributed. + if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null && !ownedObject.IsOwnershipSessionOwner) { - // Only NetworkObjects that have the OwnershipStatus.Distributable flag set and no parent - // (ownership is transferred to all children) will have their ownership redistributed. - if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null && !ownedObject.IsOwnershipSessionOwner) + if (ownedObject.IsOwnershipLocked) { - if (ownedObject.IsOwnershipLocked) + ownedObject.SetOwnershipLock(false); + } + + // DANGO-TODO: We will want to match how the CMB service handles this. For now, we just try to evenly distribute + // ownership. + var targetOwner = NetworkManager.ServerClientId; + if (predictedClientCount > 1) + { + clientCounter++; + clientCounter = clientCounter % predictedClientCount; + targetOwner = remainingClients[clientCounter].ClientId; + } + if (EnableDistributeLogging) + { + Debug.Log($"[Disconnected][Client-{clientId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); + } + NetworkManager.SpawnManager.ChangeOwnership(ownedObject, targetOwner, true); + // DANGO-TODO: Should we try handling inactive NetworkObjects? + // Ownership gets passed down to all children + var childNetworkObjects = ownedObject.GetComponentsInChildren(); + foreach (var childObject in childNetworkObjects) + { + // We already changed ownership for this + if (childObject == ownedObject) { - ownedObject.SetOwnershipLock(false); + continue; } - - // DANGO-TODO: We will want to match how the CMB service handles this. For now, we just try to evenly distribute - // ownership. - var targetOwner = NetworkManager.ServerClientId; - if (predictedClientCount > 1) + // If the client owner disconnected, it is ok to unlock this at this point in time. + if (childObject.IsOwnershipLocked) { - clientCounter++; - clientCounter = clientCounter % predictedClientCount; - targetOwner = remainingClients[clientCounter].ClientId; + childObject.SetOwnershipLock(false); } - if (EnableDistributeLogging) + + // Ignore session owner marked objects + if (childObject.IsOwnershipSessionOwner) { - Debug.Log($"[Disconnected][Client-{clientId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); + continue; } - NetworkManager.SpawnManager.ChangeOwnership(ownedObject, targetOwner, true); - // DANGO-TODO: Should we try handling inactive NetworkObjects? - // Ownership gets passed down to all children - var childNetworkObjects = ownedObject.GetComponentsInChildren(); - foreach (var childObject in childNetworkObjects) + NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true); + if (EnableDistributeLogging) { - // We already changed ownership for this - if (childObject == ownedObject) - { - continue; - } - // If the client owner disconnected, it is ok to unlock this at this point in time. - if (childObject.IsOwnershipLocked) - { - childObject.SetOwnershipLock(false); - } - - // Ignore session owner marked objects - if (childObject.IsOwnershipSessionOwner) - { - continue; - } - NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true); - if (EnableDistributeLogging) - { - Debug.Log($"[Disconnected][Client-{clientId}][Child of {ownedObject.NetworkObjectId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); - } + Debug.Log($"[Disconnected][Client-{clientId}][Child of {ownedObject.NetworkObjectId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); } } } - else - { - ownedObject.RemoveOwnership(); - } + } + else + { + ownedObject.RemoveOwnership(); } } } } + // TODO: Could(should?) be replaced with more memory per client, by storing the visibility foreach (var sobj in NetworkManager.SpawnManager.SpawnedObjectsList) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs index 84d9c56fec..f016286ac0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs @@ -61,6 +61,46 @@ public void Destroy(NetworkObject networkObject) } } + + internal class SpawnDespawnDestroyNotifications : NetworkBehaviour + { + + public int Despawned { get; private set; } + public int Destroyed { get; private set; } + + private bool m_WasSpawned; + + private ulong m_LocalClientId; + + public override void OnNetworkSpawn() + { + m_WasSpawned = true; + m_LocalClientId = NetworkManager.LocalClientId; + base.OnNetworkSpawn(); + } + + public override void OnNetworkDespawn() + { + Assert.True(Destroyed == 0, $"{name} on client-{m_LocalClientId} should have a destroy invocation count of 0 but it is {Destroyed}!"); + Assert.True(Despawned == 0, $"{name} on client-{m_LocalClientId} should have a despawn invocation count of 0 but it is {Despawned}!"); + Despawned++; + base.OnNetworkDespawn(); + } + + public override void OnDestroy() + { + // When the original prefabs are destroyed, we want to ignore this check (those instances are never spawned) + if (m_WasSpawned) + { + Assert.True(Despawned == 1, $"{name} on client-{m_LocalClientId} should have a despawn invocation count of 1 but it is {Despawned}!"); + Assert.True(Destroyed == 0, $"{name} on client-{m_LocalClientId} should have a destroy invocation count of 0 but it is {Destroyed}!"); + } + Destroyed++; + + base.OnDestroy(); + } + } + /// /// Mock component for testing that the client-side player is using the right /// network prefab. @@ -95,7 +135,9 @@ protected override void OnServerAndClientsCreated() { // Create a NetworkPrefab with an override var basePrefab = NetcodeIntegrationTestHelpers.CreateNetworkObject($"{k_PrefabRootName}-base", m_ServerNetworkManager, true); + basePrefab.AddComponent(); var targetPrefab = NetcodeIntegrationTestHelpers.CreateNetworkObject($"{k_PrefabRootName}-over", m_ServerNetworkManager, true); + targetPrefab.AddComponent(); m_PrefabOverride = new NetworkPrefab() { Prefab = basePrefab, @@ -223,6 +265,7 @@ public IEnumerator PrefabOverrideTests() { networkManagerOwner = m_ClientNetworkManagers[0]; } + // Clients and Host will spawn the OverridingTargetPrefab while a dedicated server will spawn the SourcePrefabToOverride var expectedServerGlobalObjectIdHash = networkManagerOwner.IsClient ? m_PrefabOverride.OverridingTargetPrefab.GetComponent().GlobalObjectIdHash : m_PrefabOverride.SourcePrefabToOverride.GetComponent().GlobalObjectIdHash; var expectedClientGlobalObjectIdHash = m_PrefabOverride.OverridingTargetPrefab.GetComponent().GlobalObjectIdHash; @@ -263,6 +306,40 @@ bool ObjectSpawnedOnAllNetworkMangers() yield return WaitForConditionOrTimeOut(ObjectSpawnedOnAllNetworkMangers); AssertOnTimeout($"The spawned prefab override validation failed!\n {builder}"); + + // Verify that the despawn and destroy order of operations is correct for client owned NetworkObjects and the nunmber of times each is invoked is correct + expectedServerGlobalObjectIdHash = networkManagerOwner.IsClient ? m_PrefabOverride.OverridingTargetPrefab.GetComponent().GlobalObjectIdHash : m_PrefabOverride.SourcePrefabToOverride.GetComponent().GlobalObjectIdHash; + expectedClientGlobalObjectIdHash = m_PrefabOverride.OverridingTargetPrefab.GetComponent().GlobalObjectIdHash; + + spawnedInstance = NetworkObject.InstantiateAndSpawn(m_PrefabOverride.SourcePrefabToOverride, networkManagerOwner, m_ClientNetworkManagers[0].LocalClientId); + + + yield return WaitForConditionOrTimeOut(ObjectSpawnedOnAllNetworkMangers); + AssertOnTimeout($"The spawned prefab override validation failed!\n {builder}"); + var clientId = m_ClientNetworkManagers[0].LocalClientId; + m_ClientNetworkManagers[0].Shutdown(); + + // Wait until all of the client's owned objects are destroyed + // If no asserts occur, then the despawn & destroy order of operations and invocation count is correct + /// For more information look at: + bool ClientDisconnected(ulong clientId) + { + var clientOwnedObjects = m_ServerNetworkManager.SpawnManager.SpawnedObjects.Where((c) => c.Value.OwnerClientId == clientId).ToList(); + if (clientOwnedObjects.Count > 0) + { + return false; + } + + clientOwnedObjects = m_ClientNetworkManagers[1].SpawnManager.SpawnedObjects.Where((c) => c.Value.OwnerClientId == clientId).ToList(); + if (clientOwnedObjects.Count > 0) + { + return false; + } + return true; + } + + yield return WaitForConditionOrTimeOut(() => ClientDisconnected(clientId)); + AssertOnTimeout($"Timed out waiting for client to disconnect!"); } } } From 248aa2145e86f1ea0f004911af2e8a17f556c09a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 10 Jan 2025 17:55:40 -0600 Subject: [PATCH 159/236] chore: remove deprecated relay package (#3201) * update updating the manifest to remove the deprecated relay package * update wrapping legacy code to prevent it from being compiled without the deprecated relay package. (we might just remove all of this script eventually) * update updating the project version and settings as well as the default prefab list. * style Fixing import ordering. * style fixing the import order --- .../Assets/DefaultNetworkPrefabs.asset | 8 +-- .../Assets/Scripts/ConnectionModeScript.cs | 26 +++++---- testproject/Assets/Scripts/RelayUtility.cs | 2 + testproject/Assets/Scripts/UIController.cs | 10 ++-- testproject/Packages/manifest.json | 17 +++--- .../ProjectSettings/ProjectSettings.asset | 54 ++++++++++++------- .../ProjectSettings/ProjectVersion.txt | 4 +- 7 files changed, 72 insertions(+), 49 deletions(-) diff --git a/testproject/Assets/DefaultNetworkPrefabs.asset b/testproject/Assets/DefaultNetworkPrefabs.asset index 88f490d092..a5e491547f 100644 --- a/testproject/Assets/DefaultNetworkPrefabs.asset +++ b/testproject/Assets/DefaultNetworkPrefabs.asset @@ -219,13 +219,7 @@ MonoBehaviour: SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} - Override: 0 - Prefab: {fileID: 8133991607019124060, guid: 421bcf732fe69486d8abecfa5eee63bb, - type: 3} - SourcePrefabToOverride: {fileID: 0} - SourceHashToOverride: 0 - OverridingTargetPrefab: {fileID: 0} - - Override: 0 - Prefab: {fileID: 3439633038736912633, guid: 398aad09d8b2a47eba664a076763cdcc, + Prefab: {fileID: 3830151999068797299, guid: 074ae41741b33f84ca886d5b8dc482d2, type: 3} SourcePrefabToOverride: {fileID: 0} SourceHashToOverride: 0 diff --git a/testproject/Assets/Scripts/ConnectionModeScript.cs b/testproject/Assets/Scripts/ConnectionModeScript.cs index 70bc46a1d9..c7dc884113 100644 --- a/testproject/Assets/Scripts/ConnectionModeScript.cs +++ b/testproject/Assets/Scripts/ConnectionModeScript.cs @@ -1,13 +1,16 @@ +#if ENABLE_RELAY_SERVICE +using System; +#endif using System.Collections; -using UnityEngine; using Unity.Netcode; using Unity.Netcode.Transports.UTP; -using UnityEngine.SceneManagement; #if ENABLE_RELAY_SERVICE -using System; using Unity.Services.Core; using Unity.Services.Authentication; #endif +using UnityEngine; +using UnityEngine.SceneManagement; + /// /// Used in tandem with the ConnectModeButtons prefab asset in test project @@ -23,8 +26,10 @@ public class ConnectionModeScript : MonoBehaviour [SerializeField] private GameObject m_JoinCodeInput; +#if ENABLE_RELAY_SERVICE [SerializeField] private int m_MaxConnections = 10; +#endif [SerializeField] private LoadSceneMode m_ClientSynchronizationMode; @@ -126,8 +131,10 @@ private void OnServicesInitialized() if (HasRelaySupport()) { m_JoinCodeInput.SetActive(true); +#if ENABLE_RELAY_SERVICE m_ConnectionModeButtons?.SetActive(false || AuthenticationService.Instance.IsSignedIn); m_AuthenticationButtons?.SetActive(NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && !AuthenticationService.Instance.IsSignedIn); +#endif } } @@ -138,11 +145,13 @@ public void OnStartServerButton() { if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && m_ConnectionModeButtons) { +#if ENABLE_RELAY_SERVICE if (HasRelaySupport()) { StartCoroutine(StartRelayServer(StartServer)); } else +#endif { StartServer(); } @@ -170,13 +179,13 @@ private void OnServerStopped(bool obj) } } - +#if ENABLE_RELAY_SERVICE /// /// Coroutine that handles starting MLAPI in server mode if Relay is enabled /// private IEnumerator StartRelayServer(Action postAllocationAction) { -#if ENABLE_RELAY_SERVICE + m_ConnectionModeButtons?.SetActive(false); var serverRelayUtilityTask = RelayUtility.AllocateRelayServerAndGetJoinCode(m_MaxConnections); @@ -198,11 +207,8 @@ private IEnumerator StartRelayServer(Action postAllocationAction) NetworkManager.Singleton.GetComponent().SetRelayServerData(ipv4address, port, allocationIdBytes, key, connectionData); postAllocationAction(); -#else - yield return null; -#endif } - +#endif /// /// Handles starting netcode in host mode @@ -211,11 +217,13 @@ public void OnStartHostButton() { if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && m_ConnectionModeButtons) { +#if ENABLE_RELAY_SERVICE if (HasRelaySupport()) { StartCoroutine(StartRelayServer(StartHost)); } else +#endif { StartHost(); } diff --git a/testproject/Assets/Scripts/RelayUtility.cs b/testproject/Assets/Scripts/RelayUtility.cs index 4935a2fdf1..abd32efffd 100644 --- a/testproject/Assets/Scripts/RelayUtility.cs +++ b/testproject/Assets/Scripts/RelayUtility.cs @@ -1,3 +1,4 @@ +#if ENABLE_RELAY_SERVICE using System; using System.Threading.Tasks; using Unity.Services.Relay; @@ -56,3 +57,4 @@ public class RelayUtility return (allocation.RelayServer.IpV4, (ushort)allocation.RelayServer.Port, allocation.AllocationIdBytes, allocation.ConnectionData, allocation.HostConnectionData, allocation.Key); } } +#endif diff --git a/testproject/Assets/Scripts/UIController.cs b/testproject/Assets/Scripts/UIController.cs index 3cb27e09bb..f83d667bf9 100644 --- a/testproject/Assets/Scripts/UIController.cs +++ b/testproject/Assets/Scripts/UIController.cs @@ -1,10 +1,11 @@ -using UnityEngine; using Unity.Netcode; using Unity.Netcode.Transports.UTP; #if ENABLE_RELAY_SERVICE using Unity.Services.Core; using Unity.Services.Authentication; #endif +using UnityEngine; + public class UIController : MonoBehaviour { @@ -49,10 +50,10 @@ private void HideButtons() ButtonsRoot.SetActive(false); } - +#if ENABLE_RELAY_SERVICE public async void OnSignIn() { -#if ENABLE_RELAY_SERVICE + await UnityServices.InitializeAsync(); Debug.Log("OnSignIn"); await AuthenticationService.Instance.SignInAnonymouslyAsync(); @@ -64,6 +65,7 @@ public async void OnSignIn() JoinCode.SetActive(true); AuthButton.SetActive(false); } -#endif + } +#endif } diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 40f3385f51..1a765a9f04 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,20 +1,19 @@ { "dependencies": { - "com.unity.addressables": "2.1.0", - "com.unity.ai.navigation": "2.0.0", - "com.unity.collab-proxy": "2.4.3", - "com.unity.ide.rider": "3.0.28", + "com.unity.addressables": "2.2.2", + "com.unity.ai.navigation": "2.0.5", + "com.unity.collab-proxy": "2.6.0", + "com.unity.ide.rider": "3.0.31", "com.unity.ide.visualstudio": "2.0.22", "com.unity.mathematics": "1.3.2", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.49.0-preview", - "com.unity.services.authentication": "3.3.3", - "com.unity.services.core": "1.13.0", - "com.unity.services.relay": "1.0.5", - "com.unity.test-framework": "1.4.4", + "com.unity.services.authentication": "3.4.0", + "com.unity.services.core": "1.14.0", + "com.unity.test-framework": "1.4.5", "com.unity.test-framework.performance": "3.0.3", "com.unity.timeline": "1.8.7", - "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.9", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", "com.unity.ugui": "2.0.0", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 43f16b8278..9975ea1a15 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -3,7 +3,7 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 26 + serializedVersion: 28 productGUID: bba99b16607b94720b7d04f7f1a82989 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 @@ -49,12 +49,13 @@ PlayerSettings: m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 unsupportedMSAAFallback: 0 + m_SpriteBatchMaxVertexCount: 65535 m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 mipStripping: 0 numberOfMipsStripped: 0 numberOfMipsStrippedPerMipmapLimitGroup: {} - m_StackTraceTypes: 010000000100000001000000000000000100000001000000 + m_StackTraceTypes: 020000000200000001000000010000000200000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 @@ -70,17 +71,18 @@ PlayerSettings: androidRenderOutsideSafeArea: 1 androidUseSwappy: 1 androidBlitType: 0 - androidResizableWindow: 0 + androidResizeableActivity: 0 androidDefaultWindowWidth: 1920 androidDefaultWindowHeight: 1080 androidMinimumWindowWidth: 400 androidMinimumWindowHeight: 300 androidFullscreenMode: 1 androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 0 + androidApplicationEntry: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 - captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 @@ -96,6 +98,7 @@ PlayerSettings: useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 1 + meshDeformation: 2 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 @@ -127,10 +130,8 @@ PlayerSettings: switchAllowGpuScratchShrinking: 0 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 - switchNVNGraphicsFirmwareMemory: 32 switchMaxWorkerMultiple: 8 - stadiaPresentMode: 0 - stadiaTargetFramerate: 0 + switchNVNGraphicsFirmwareMemory: 32 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 @@ -160,6 +161,7 @@ PlayerSettings: resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 + androidMinAspectRatio: 1 applicationIdentifier: Standalone: com.Unity-Technologies.testproject buildNumber: @@ -169,7 +171,7 @@ PlayerSettings: tvOS: 0 overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 22 + AndroidMinSdkVersion: 23 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: nimt-trampolines=1024 @@ -179,16 +181,18 @@ PlayerSettings: ForceInternetPermission: 0 ForceSDCardPermission: 0 CreateWallpaper: 0 - APKExpansionFiles: 0 + androidSplitApplicationBinary: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 1 strictShaderVariantMatching: 0 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 - iOSTargetOSVersionString: 12.0 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 13.0 tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 12.0 + tvOSTargetOSVersionString: 13.0 VisionOSSdkVersion: 0 VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 @@ -215,7 +219,6 @@ PlayerSettings: rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 - iOSLaunchScreenCustomXibPath: iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: @@ -223,7 +226,6 @@ PlayerSettings: rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 - iOSLaunchScreeniPadCustomXibPath: iOSLaunchScreenCustomStoryboardPath: iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] @@ -233,6 +235,7 @@ PlayerSettings: iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 + metalCompileShaderBinary: 0 iOSRenderExtraFrameOnPause: 0 iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: @@ -259,12 +262,12 @@ PlayerSettings: useCustomGradleSettingsTemplate: 0 useCustomProguardFile: 0 AndroidTargetArchitectures: 1 - AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} AndroidKeystoreName: AndroidKeyaliasName: AndroidEnableArmv9SecurityFeatures: 0 + AndroidEnableArm64MTE: 0 AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 0 AndroidIsGame: 1 @@ -277,11 +280,12 @@ PlayerSettings: height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 - chromeosInputEmulation: 1 AndroidMinifyRelease: 0 AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 150 + AndroidReportGooglePlayAppDependencies: 1 + androidSymbolsSizeThreshold: 800 m_BuildTargetIcons: [] m_BuildTargetPlatformIcons: [] m_BuildTargetBatching: @@ -363,7 +367,6 @@ PlayerSettings: iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] - m_BuildTargetGroupHDRCubemapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] m_BuildTargetGroupLoadStoreDebugModeSettings: [] m_BuildTargetNormalMapEncoding: [] @@ -371,6 +374,7 @@ PlayerSettings: playModeTestRunnerEnabled: 1 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 + editorGfxJobOverride: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 @@ -378,7 +382,7 @@ PlayerSettings: locationUsageDescription: microphoneUsageDescription: bluetoothUsageDescription: - macOSTargetOSVersion: 10.13.0 + macOSTargetOSVersion: 11.0 switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 @@ -516,12 +520,14 @@ PlayerSettings: switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 switchUseNewStyleFilepaths: 0 switchUseLegacyFmodPriorities: 0 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 switchRamDiskSpaceSize: 12 + switchUpgradedPlayerSettingsToNMETA: 0 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: @@ -624,7 +630,12 @@ PlayerSettings: webGLMemoryLinearGrowthStep: 16 webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthCap: 96 + webGLEnableWebGPU: 0 webGLPowerPreference: 2 + webGLWebAssemblyTable: 0 + webGLWebAssemblyBigInt: 0 + webGLCloseOnQuit: 0 + webWasm2023: 0 scriptingDefineSymbols: Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT additionalCompilerArguments: @@ -634,6 +645,7 @@ PlayerSettings: scriptingBackend: {} il2cppCompilerConfiguration: {} il2cppCodeGeneration: {} + il2cppStacktraceInformation: {} managedStrippingLevel: EmbeddedLinux: 1 GameCoreScarlett: 1 @@ -658,6 +670,7 @@ PlayerSettings: gcIncremental: 1 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} + editorAssembliesCompatibilityLevel: 1 m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: Template3D @@ -682,6 +695,7 @@ PlayerSettings: metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} metroFTAName: @@ -739,9 +753,11 @@ PlayerSettings: hmiPlayerDataPath: hmiForceSRGBBlit: 1 embeddedLinuxEnableGamepadInput: 1 - hmiLogStartupTiming: 0 hmiCpuConfiguration: + hmiLogStartupTiming: 0 + qnxGraphicConfPath: apiCompatibilityLevel: 6 + captureStartupLogs: {} activeInputHandler: 0 windowsGamepadBackendHint: 0 cloudProjectId: @@ -755,3 +771,5 @@ PlayerSettings: platformRequiresReadableAssets: 0 virtualTexturingSupportEnabled: 0 insecureHttpOption: 0 + androidVulkanDenyFilterList: [] + androidVulkanAllowFilterList: [] diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index 11b73a4be2..d1c9097a6c 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.23f1 -m_EditorVersionWithRevision: 6000.0.23f1 (1c4764c07fb4) +m_EditorVersion: 6000.0.32f1 +m_EditorVersionWithRevision: 6000.0.32f1 (b2e806cf271c) From 12498431e0db50ba56e168aabaf93a13d6a7cecb Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 15 Jan 2025 18:10:47 -0600 Subject: [PATCH 160/236] chore: update example overriding scenes networkprefabs dedicated server (#3207) * update Adding support for a dedicated server build. * update adding windows dedicated server build example with DEDICATED_SERVER define --- .../Assets/Scripts/MoverScriptNoRigidbody.cs | 6 + .../Scripts/NetworkManagerBootstrapper.cs | 205 +++++++++++++++++- .../Scripts/NetworkPrefabOverrideHandler.cs | 2 + .../Assets/Scripts/SceneBootstrapLoader.cs | 13 +- .../Assets/Scripts/ServerHostClientText.cs | 3 + .../Assets/Scripts/ServerInfoDisplay.cs | 5 +- .../Assets/Settings.meta | 8 + .../Assets/Settings/Build Profiles.meta | 8 + .../New Windows Server Profile.asset | 49 +++++ .../New Windows Server Profile.asset.meta | 8 + 10 files changed, 293 insertions(+), 14 deletions(-) create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Settings.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles.meta create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset create mode 100644 Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset.meta diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs index abc1f5e0fc..5cc3865bfd 100644 --- a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/MoverScriptNoRigidbody.cs @@ -174,7 +174,9 @@ protected override void OnNetworkPostSpawn() } m_ParentedText?.gameObject.SetActive(true); +#if !DEDICATED_SERVER UpdateParentedText(); +#endif base.OnNetworkPostSpawn(); } @@ -201,7 +203,9 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj { Debug.Log($"Parented under {parentNetworkObject.name}"); } +#if !DEDICATED_SERVER UpdateParentedText(); +#endif base.OnNetworkObjectParentChanged(parentNetworkObject); } @@ -334,6 +338,7 @@ private void ApplyInput() m_PlayerBallMotion.HasMotion(moveMotion); } +#if !DEDICATED_SERVER /// /// Updates player TextMesh relative to each client's camera view /// @@ -373,4 +378,5 @@ private void UpdateParentedText() } } } +#endif } diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs index bedc3ed758..da858b14ef 100644 --- a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkManagerBootstrapper.cs @@ -1,13 +1,23 @@ +#if !DEDICATED_SERVER || (DEDICATED_SERVER && !UNITY_EDITOR) using System; +#endif using System.Collections.Generic; using System.Linq; +#if !DEDICATED_SERVER using System.Threading.Tasks; +#endif using Unity.Netcode; +#if !DEDICATED_SERVER using Unity.Services.Authentication; using Unity.Services.Core; using Unity.Services.Multiplayer; +#else +using Unity.Netcode.Transports.UTP; +#endif using UnityEngine; +#if !DEDICATED_SERVER using SessionState = Unity.Services.Multiplayer.SessionState; +#endif #region NetworkManagerBootstrapperEditor #if UNITY_EDITOR @@ -67,7 +77,7 @@ protected override void OnValidateComponent() base.OnValidateComponent(); } #endif - #endregion +#endregion #region Properties public static NetworkManagerBootstrapper Instance; @@ -98,12 +108,13 @@ private enum ConnectionStates [SerializeField] private bool m_ServicesRegistered; +#if !DEDICATED_SERVER private ISession m_CurrentSession; private string m_SessionName; private string m_ProfileName; private Task m_SessionTask; - - #endregion +#endif +#endregion #region Initialization and Destroy public static string GetRandomString(int length) @@ -120,12 +131,27 @@ public void SetFrameRate(int targetFrameRate, bool enableVsync) private void Awake() { +#if !DEDICATED_SERVER Screen.SetResolution((int)(Screen.currentResolution.width * 0.40f), (int)(Screen.currentResolution.height * 0.40f), FullScreenMode.Windowed); SetFrameRate(TargetFrameRate, EnableVSync); +#endif SetSingleton(); m_SceneBootstrapLoader = GetComponent(); } +#if DEDICATED_SERVER + private void Start() + { + m_SceneBootstrapLoader.OnMainMenuLoaded += OnMainMenuLoaded; + m_SceneBootstrapLoader.LoadMainMenu(); + } + + private void OnMainMenuLoaded() + { + m_SceneBootstrapLoader.OnMainMenuLoaded -= OnMainMenuLoaded; + StartDedicatedServer(); + } +#else private async void Start() { OnClientConnectedCallback += OnClientConnected; @@ -152,17 +178,20 @@ private async void Start() } } m_SceneBootstrapLoader.LoadMainMenu(); - } + } private void OnDestroy() { OnClientConnectedCallback -= OnClientConnected; OnClientDisconnectCallback -= OnClientDisconnect; OnConnectionEvent -= OnClientConnectionEvent; } - #endregion +#endif +#endregion #region Session and Connection Event Handling + +#if !DEDICATED_SERVER private void OnClientConnectionEvent(NetworkManager networkManager, ConnectionEventData eventData) { LogMessage($"[{Time.realtimeSinceStartup}] Connection event {eventData.EventType} for Client-{eventData.ClientId}."); @@ -235,9 +264,11 @@ private async Task ConnectThroughLiveService() } return null; } - #endregion +#endif +#endregion #region GUI Menu +#if !DEDICATED_SERVER public void StartOrConnectToDistributedAuthoritySession() { m_SessionTask = ConnectThroughLiveService(); @@ -364,9 +395,11 @@ private void OnGUI() } GUILayout.EndArea(); } - #endregion +#endif +#endregion #region Server Camera Handling +#if !DEDICATED_SERVER private Vector3 m_CameraOriginalPosition; private Quaternion m_CameraOriginalRotation; private int m_CurrentFollowPlayerIndex = -1; @@ -438,9 +471,159 @@ public void ClearFollowPlayer() SetCameraDefaults(); } } - #endregion +#endif +#endregion #region Update Methods and Properties +#if DEDICATED_SERVER + private UnityTransport m_UnityTransport; + /// + /// All of the dedicated server specific script logic is contained and only compiled when DEDICATED_SERVER is defined + /// + + private void StartDedicatedServer() + { + m_UnityTransport = NetworkConfig.NetworkTransport as UnityTransport; + if (m_UnityTransport != null) + { + // Always good to know what scenes are currently loaded since you might have + // different scenes to load for a DGS vs client + var scenesPreloaded = new System.Text.StringBuilder(); + scenesPreloaded.Append("Scenes Preloaded: "); + for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) + { + var scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + scenesPreloaded.Append($"[{scene.name}]"); + } + Debug.Log(scenesPreloaded.ToString()); + + // Set the application frame rate to like 30 to reduce frame processing overhead + Application.targetFrameRate = 30; + + Debug.Log($"[Pre-Init] Server Address Endpoint: {m_UnityTransport.ConnectionData.ServerEndPoint}"); + Debug.Log($"[Pre-Init] Server Listen Endpoint: {m_UnityTransport.ConnectionData.ListenEndPoint}"); + // Setup your IP and port sepcific to your DGS + //unityTransport.SetConnectionData(ListenAddress, ListenPort, ListenAddress); + + //Debug.Log($"[Post-Init] Server Address Endpoint: {unityTransport.ConnectionData.ServerEndPoint}"); + //Debug.Log($"[Post-Init] Server Listen Endpoint: {unityTransport.ConnectionData.ListenEndPoint}"); + + // Get the server started notification + OnServerStarted += ServerStarted; + + // Start the server listening + m_SceneBootstrapLoader.StartSession(SceneBootstrapLoader.StartAsTypes.Server); + } + else + { + Debug.LogError("Failed to get the UnityTransport!"); + } + } + + /// + /// Register callbacks when the OnServerStarted callback is invoked. + /// This makes it easier to know you are registering for events only + /// when the server successfully has started. + /// + private void ServerStarted() + { + Debug.Log("Dedicated Server Started!"); + Debug.Log($"[Started] Server Address Endpoint: {m_UnityTransport.ConnectionData.ServerEndPoint}"); + Debug.Log($"[Started] Server Listen Endpoint: {m_UnityTransport.ConnectionData.ListenEndPoint}"); + Debug.Log("==============================================================="); + Debug.Log("[X] Exits session (Shutdown) | [ESC] Exits application instance"); + Debug.Log("==============================================================="); + OnServerStarted -= ServerStarted; + OnClientConnectedCallback += ClientConnectedCallback; + OnClientDisconnectCallback += ClientDisconnectCallback; + // Register for OnServerStopped + OnServerStopped += ServerStopped; + } + + private void ServerStopped(bool obj) + { + OnClientConnectedCallback -= ClientConnectedCallback; + OnClientDisconnectCallback -= ClientDisconnectCallback; + OnServerStopped -= ServerStopped; + Debug.Log("Dedicated Server Stopped!"); + Debug.Log("==============================================================="); + Debug.Log("[S] Starts new session (StartServer) | [ESC] Exits application"); + Debug.Log("==============================================================="); + } + + private void ClientConnectedCallback(ulong clientId) + { + Debug.Log($"Client-{clientId} connected and approved."); + } + + private void ClientDisconnectCallback(ulong clientId) + { + Debug.Log($"Client-{clientId} disconnected."); + } + +#if UNITY_EDITOR + private void HandleEditorKeyCommands() + { + // Shutdown/Stop the server + if (Input.GetKeyDown(KeyCode.X) && IsListening && !ShutdownInProgress) + { + Shutdown(); + } + else // Start the server (this example makes it automatically start when the application first starts) + if (Input.GetKeyDown(KeyCode.S) && !IsListening) + { + StartDedicatedServer(); + } + } +#else + private void HandleConsoleKeyCommands() + { + if (Console.KeyAvailable) + { + var networkManager = NetworkManager.Singleton; + var keyPressed = Console.ReadKey(true); + switch(keyPressed.Key) + { + case ConsoleKey.X: + { + if(networkManager.IsListening && !networkManager.ShutdownInProgress) + { + networkManager.Shutdown(); + } + break; + } + case ConsoleKey.S: + { + if (!networkManager.IsListening && !networkManager.ShutdownInProgress) + { + StartDedicatedServer(); + } + break; + } + case ConsoleKey.Escape: + { + Application.Quit(); + break; + } + } + } + } +#endif + + /// + /// Update that is only included in the build and invoked when running as a dedicated server + /// + private void DedicatedServerUpdate() + { +#if UNITY_EDITOR + HandleEditorKeyCommands(); +#else + HandleConsoleKeyCommands(); +#endif + } + +#else // End of DEDICATED_SERVER defined region + /// /// General update for server-side /// @@ -459,9 +642,13 @@ private void ClientSideUpdate() { } +#endif private void Update() { +#if DEDICATED_SERVER + DedicatedServerUpdate(); +#else if (IsListening) { if (IsServer) @@ -473,6 +660,7 @@ private void Update() ClientSideUpdate(); } } +#endif if (m_MessageLogs.Count == 0) { @@ -487,6 +675,7 @@ private void Update() } } } + #endregion #region Message Logging diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs index 7774f52d71..9bba90b13a 100644 --- a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/NetworkPrefabOverrideHandler.cs @@ -52,6 +52,7 @@ public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaterni public void Destroy(NetworkObject networkObject) { +#if !DEDICATED_SERVER // Another useful thing about handling this instantiation and destruction of a NetworkObject is that you can do house cleaning // prior to the object being destroyed. This handles the scenario where the server is following a player and the player disconnects. // Before destroying the player object, we want to unparent the camera and reset the player being followed. @@ -59,6 +60,7 @@ public void Destroy(NetworkObject networkObject) { m_NetworkManager.ClearFollowPlayer(); } +#endif Destroy(networkObject.gameObject); } } diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs index 08642c10e1..256122618e 100644 --- a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/SceneBootstrapLoader.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using Unity.Netcode; - using UnityEngine; using UnityEngine.SceneManagement; #if UNITY_EDITOR @@ -105,6 +104,8 @@ private void Awake() m_NetworkManager = GetComponent(); } + public event Action OnMainMenuLoaded; + /// /// Should be invoked by bootstrap when first starting the applicaiton and should be loaded upon exiting /// a session and shutting down the . @@ -114,6 +115,7 @@ public void LoadMainMenu() if (!m_NetworkManager.IsListening) { SceneManager.LoadScene(m_MainMenuScene, LoadSceneMode.Single); + OnMainMenuLoaded?.Invoke(); } else { @@ -147,9 +149,6 @@ private void OnNetworkManagerShutdown(bool wasHost) } #region SCENE PRE & POST START LOADING METHODS - - - private IEnumerator PreSceneLoading(StartAsTypes startAsType) { var sceneDefines = startAsType == StartAsTypes.Client ? ClientSceneDefines : ServerSceneDefines; @@ -188,11 +187,14 @@ private IEnumerator PreSceneLoading(StartAsTypes startAsType) { m_NetworkManager.StartServer(); } +#if !DEDICATED_SERVER else { m_NetworkManager.StartHost(); } +#endif } +#if !DEDICATED_SERVER else { m_NetworkManager.OnClientStopped += OnNetworkManagerShutdown; @@ -209,6 +211,7 @@ private IEnumerator PreSceneLoading(StartAsTypes startAsType) m_NetworkManager.StartClient(); } } +#endif } /// @@ -265,7 +268,7 @@ private void SceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { m_SceneJustLoaded = scene.name; } - #endregion +#endregion #region SERVER POST START CONFIGURATION AND ADDITIONAL SHARED SCENE LOADING /// diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs index 93e150b2ba..1319e54b85 100644 --- a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerHostClientText.cs @@ -63,7 +63,9 @@ public override void OnNetworkDespawn() base.OnNetworkDespawn(); } +#if !DEDICATED_SERVER private bool m_LastFocusedValue; + private void OnGUI() { if (!IsSpawned || m_LastFocusedValue == Application.isFocused) @@ -82,4 +84,5 @@ private void OnGUI() m_DisplayText.color = m_ColorAlpha; } } +#endif } diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs index ebc84d8d98..c4025fefc4 100644 --- a/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs +++ b/Examples/OverridingScenesAndPrefabs/Assets/Scripts/ServerInfoDisplay.cs @@ -1,4 +1,6 @@ +#if !DEDICATED_SERVER using Unity.Netcode; +#endif using UnityEngine; using UnityEngine.UI; @@ -6,7 +8,7 @@ public class ServerInfoDisplay : MonoBehaviour { public Text ServerTime; public Text PlayerCount; - +#if !DEDICATED_SERVER private void OnGUI() { if (!NetworkManager.Singleton || !NetworkManager.Singleton.IsListening) @@ -24,4 +26,5 @@ private void OnGUI() PlayerCount.text = $"Player Count: {NetworkManager.Singleton.ConnectedClients.Count}"; } } +#endif } diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Settings.meta b/Examples/OverridingScenesAndPrefabs/Assets/Settings.meta new file mode 100644 index 0000000000..bf54907342 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f07d39c4f18773945af7943acf04dff7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles.meta b/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles.meta new file mode 100644 index 0000000000..c47c89da39 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 38b8655f79da8bd4ea13982ac8833b71 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset b/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset new file mode 100644 index 0000000000..b5b4979d3e --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset @@ -0,0 +1,49 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0} + m_Name: New Windows Server Profile + m_EditorClassIdentifier: + m_AssetVersion: 1 + m_BuildTarget: 19 + m_Subtarget: 1 + m_PlatformId: 8d1e1bca926649cba89d37a4c66e8b3d + m_PlatformBuildProfile: + rid: 4481205133896843345 + m_OverrideGlobalSceneList: 0 + m_Scenes: [] + m_ScriptingDefines: + - DEDICATED_SERVER + m_PlayerSettingsYaml: + m_Settings: [] + references: + version: 2 + RefIds: + - rid: 4481205133896843345 + type: {class: WindowsPlatformSettings, ns: UnityEditor.WindowsStandalone, asm: UnityEditor.WindowsStandalone.Extensions} + data: + m_Development: 0 + m_ConnectProfiler: 0 + m_BuildWithDeepProfilingSupport: 0 + m_AllowDebugging: 0 + m_WaitForManagedDebugger: 0 + m_ManagedDebuggerFixedPort: 0 + m_ExplicitNullChecks: 0 + m_ExplicitDivideByZeroChecks: 0 + m_ExplicitArrayBoundsChecks: 0 + m_CompressionType: 0 + m_InstallInBuildFolder: 0 + m_WindowsBuildAndRunDeployTarget: 0 + m_Architecture: 0 + m_CreateSolution: 0 + m_CopyPDBFiles: 0 + m_WindowsDevicePortalAddress: + m_WindowsDevicePortalUsername: diff --git a/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset.meta b/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset.meta new file mode 100644 index 0000000000..6543ae07a8 --- /dev/null +++ b/Examples/OverridingScenesAndPrefabs/Assets/Settings/Build Profiles/New Windows Server Profile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ceabbe1f96a84864d8cecdc70db0a1f8 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: From 870452b7831ab02c8b3f428014247a55177dc4f5 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 24 Jan 2025 10:49:50 -0600 Subject: [PATCH 161/236] fix: more accurate local time sync with server time (up-port) (#3212) * get a more accurate local time by using half the RTT. * test Adjusting ClientNetworkTimeSystemTests to account for half RTT adjustment applied in this PR. * style Adding a comment about the change * update Adding changelog entry * style removing white space after comment --------- Co-authored-by: CTHULHU\Ben --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Timing/NetworkTimeSystem.cs | 4 +- .../Timing/ClientNetworkTimeSystemTests.cs | 55 +++++++++++-------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c20a80df87..8f2dee46b1 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 4dbd85a046..41327dbcd2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -248,7 +248,9 @@ public void Sync(double serverTimeSec, double rttSec) var timeDif = serverTimeSec - m_TimeSec; m_DesiredServerTimeOffset = timeDif - ServerBufferSec; - m_DesiredLocalTimeOffset = timeDif + rttSec + LocalBufferSec; + // We adjust our desired local time offset to be half RTT since the delivery of + // the TimeSyncMessage should only take half of the RTT time (legacy was using 1 full RTT) + m_DesiredLocalTimeOffset = timeDif + (rttSec * 0.5d) + LocalBufferSec; } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs index 275e891efa..fc7cb01d69 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Timing/ClientNetworkTimeSystemTests.cs @@ -18,15 +18,16 @@ internal class ClientNetworkTimeSystemTests public void StableRttTest() { double receivedServerTime = 2; - - var timeSystem = new NetworkTimeSystem(0.05d, 0.05d, 0.1d); + var baseRtt = 0.1f; + var halfRtt = 0.05f; + var timeSystem = new NetworkTimeSystem(0.05d, 0.05d, baseRtt); timeSystem.Reset(receivedServerTime, 0.15); var tickSystem = new NetworkTickSystem(60, timeSystem.LocalTime, timeSystem.ServerTime); Assert.True(timeSystem.LocalTime > 2); - var steps = TimingTestHelper.GetRandomTimeSteps(100f, 0.01f, 0.1f, 42); - var rttSteps = TimingTestHelper.GetRandomTimeSteps(1000f, 0.095f, 0.105f, 42); // 10ms jitter + var steps = TimingTestHelper.GetRandomTimeSteps(100f, 0.01f, baseRtt, 42); + var rttSteps = TimingTestHelper.GetRandomTimeSteps(1000f, baseRtt - 0.05f, baseRtt + 0.05f, 42); // 10ms jitter // run for a while so that we reach regular RTT offset TimingTestHelper.ApplySteps(timeSystem, tickSystem, steps, delegate (int step) @@ -37,10 +38,11 @@ public void StableRttTest() }); // check how we close we are to target time. - var expectedRtt = 0.1d; - var offsetToTarget = (timeSystem.LocalTime - timeSystem.ServerTime) - expectedRtt - timeSystem.ServerBufferSec - timeSystem.LocalBufferSec; + var offsetToTarget = (timeSystem.LocalTime - timeSystem.ServerTime) - halfRtt - timeSystem.ServerBufferSec - timeSystem.LocalBufferSec; Debug.Log($"offset to target time after running for a while: {offsetToTarget}"); - Assert.IsTrue(Math.Abs(offsetToTarget) < k_AcceptableRttOffset); + + // server speedup/slowdowns should not be affected by RTT + Assert.True(Math.Abs(offsetToTarget) < k_AcceptableRttOffset, $"Expected offset time to be less than {k_AcceptableRttOffset}ms but it was {offsetToTarget}!"); // run again, test that we never need to speed up or slow down under stable RTT TimingTestHelper.ApplySteps(timeSystem, tickSystem, steps, delegate (int step) @@ -51,9 +53,10 @@ public void StableRttTest() }); // check again to ensure we are still close to the target - var newOffsetToTarget = (timeSystem.LocalTime - timeSystem.ServerTime) - expectedRtt - timeSystem.ServerBufferSec - timeSystem.LocalBufferSec; + var newOffsetToTarget = (timeSystem.LocalTime - timeSystem.ServerTime) - halfRtt - timeSystem.ServerBufferSec - timeSystem.LocalBufferSec; Debug.Log($"offset to target time after running longer: {newOffsetToTarget}"); - Assert.IsTrue(Math.Abs(newOffsetToTarget) < k_AcceptableRttOffset); + // server speedup/slowdowns should not be affected by RTT + Assert.True(Math.Abs(offsetToTarget) < k_AcceptableRttOffset, $"Expected offset time to be less than {k_AcceptableRttOffset}ms but it was {offsetToTarget}!"); // difference between first and second offset should be minimal var dif = offsetToTarget - newOffsetToTarget; @@ -67,13 +70,14 @@ public void StableRttTest() public void RttCatchupSlowdownTest() { double receivedServerTime = 2; - - var timeSystem = new NetworkTimeSystem(0.05d, 0.05d, 0.1d); + var baseRtt = 0.1f; + var halfRtt = 0.05f; + var timeSystem = new NetworkTimeSystem(0.05d, 0.05d, baseRtt); timeSystem.Reset(receivedServerTime, 0.15); var tickSystem = new NetworkTickSystem(60, timeSystem.LocalTime, timeSystem.ServerTime); - var steps = TimingTestHelper.GetRandomTimeSteps(100f, 0.01f, 0.1f, 42); - var rttSteps = TimingTestHelper.GetRandomTimeSteps(1000f, 0.095f, 0.105f, 42); // 10ms jitter + var steps = TimingTestHelper.GetRandomTimeSteps(100f, 0.01f, baseRtt, 42); + var rttSteps = TimingTestHelper.GetRandomTimeSteps(1000f, baseRtt - 0.05f, baseRtt + 0.05f, 42); // 10ms jitter // run for a while so that we reach regular RTT offset TimingTestHelper.ApplySteps(timeSystem, tickSystem, steps, delegate (int step) @@ -102,11 +106,14 @@ public void RttCatchupSlowdownTest() // speed up of 0.1f expected Debug.Log($"Total local speed up time catch up: {totalLocalSpeedUpTime}"); - Assert.True(Math.Abs(totalLocalSpeedUpTime - 0.1) < k_AcceptableRttOffset); - Assert.True(Math.Abs(totalServerSpeedUpTime) < k_AcceptableRttOffset); // server speedup/slowdowns should not be affected by RTT + var expectedSpeedUpTime = Math.Abs(totalLocalSpeedUpTime - halfRtt); + var expectedServerSpeedUpTime = Math.Abs(totalServerSpeedUpTime); + Assert.True(expectedSpeedUpTime < k_AcceptableRttOffset, $"Expected local speed up time to be less than {k_AcceptableRttOffset}ms but it was {expectedSpeedUpTime}!"); + // server speedup/slowdowns should not be affected by RTT + Assert.True(Math.Abs(totalServerSpeedUpTime) < k_AcceptableRttOffset, $"Expected server speed up time to be less than {k_AcceptableRttOffset}ms but it was {expectedServerSpeedUpTime}!"); - // run again with RTT ~100ms and see whether we slow down by -0.1f + // run again with RTT ~100ms and see whether we slow down by -halfRtt unscaledLocalTime = timeSystem.LocalTime; unscaledServerTime = timeSystem.ServerTime; @@ -121,13 +128,13 @@ public void RttCatchupSlowdownTest() totalLocalSpeedUpTime = timeSystem.LocalTime - unscaledLocalTime; totalServerSpeedUpTime = timeSystem.ServerTime - unscaledServerTime; - - // slow down of 0.1f expected + // slow down of half halfRtt expected Debug.Log($"Total local speed up time slow down: {totalLocalSpeedUpTime}"); - Assert.True(Math.Abs(totalLocalSpeedUpTime + 0.1) < k_AcceptableRttOffset); - Assert.True(Math.Abs(totalServerSpeedUpTime) < k_AcceptableRttOffset); // server speedup/slowdowns should not be affected by RTT - - + expectedSpeedUpTime = Math.Abs(totalLocalSpeedUpTime + halfRtt); + expectedServerSpeedUpTime = Math.Abs(totalServerSpeedUpTime); + Assert.True(expectedSpeedUpTime < k_AcceptableRttOffset, $"Expected local speed up time to be less than {k_AcceptableRttOffset}ms but it was {expectedSpeedUpTime}!"); + // server speedup/slowdowns should not be affected by RTT + Assert.True(Math.Abs(totalServerSpeedUpTime) < k_AcceptableRttOffset, $"Expected server speed up time to be less than {k_AcceptableRttOffset}ms but it was {expectedServerSpeedUpTime}!"); } /// @@ -172,8 +179,8 @@ public void ResetTest() receivedServerTime += steps[step]; timeSystem.Sync(receivedServerTime, rttSteps2[step]); - // after hard reset time should stay close to rtt - var expectedRtt = 0.5d; + // after hard reset time should stay close to half rtt + var expectedRtt = 0.25d; Assert.IsTrue(Math.Abs((timeSystem.LocalTime - timeSystem.ServerTime) - expectedRtt - timeSystem.ServerBufferSec - timeSystem.LocalBufferSec) < k_AcceptableRttOffset); }); From 144bd7874f42ea8c3f435fd40a95440cda3130fe Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 3 Feb 2025 09:35:16 -0600 Subject: [PATCH 162/236] fix: networkvariablebase not being reinitialized if networkobject persists between sessions (#3181) * fix This will "re-initialize" NetworkVariableBase derived classes when the instance is not destroyed and repurposed. * update adding changelog entry * update Adding PR number to changelog entry * fix ? This test needs more comments. * update Changing the approach to this fix while also cleaning up the initialize process so it can be invoked when the derived class is not yet fully spawned (or just recently instantiated) without potentially assigning the wrong NetworkManager instance. It is being invoked multiple times anyway, so this just gradually initializes all of the required elements until it is finally considered "initialized". This also prevents OnInitialize from being invoked multiple times during this process and OnInitialize is only invoked as a last step to being considered "initialized". Added a Deinitialize step to NetworkVariableBase when despawning a NetworkObject where the initialized status is reset. This allows for the next spawning of the NetworkObject (if in-scene placed or pooled) to run through the initialization process. * test reverting the changes to WhenServerChangesSmoothValue_ValuesAreLerped as the recent update resolves the issue from the previous fix. * update Removing exception. Adjusting when we update the last time. Logging warning if log level is developer and there is no NetworkTimeSystem. * update remove the try catch because evidently it is the only way we can catch certain exceptions during unit testing when duplicating the value (i.e. serializing). * test This validates the fix for resetting the NetworkVariableBase.LastUpdated property when a NetworkObject is persisted (i.e. recycled via in-scene placed NetworkObject or object pools). Also minor update to prevent the integration test from logging a blank entry if there has been no logs added. * test The first iteration should ignore the time delta as that could be impacted by the test or VM running the test. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkBehaviour.cs | 27 ++ .../NetworkVariable/NetworkVariableBase.cs | 92 +++- .../Runtime/NetcodeIntegrationTest.cs | 8 +- ...orkVariableBaseInitializesWhenPersisted.cs | 412 ++++++++++++++++++ ...riableBaseInitializesWhenPersisted.cs.meta | 2 + 6 files changed, 520 insertions(+), 22 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 8f2dee46b1..514b6e994b 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) +- Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index adc7ec0fa4..da6b35e001 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -815,6 +815,13 @@ internal void InternalOnNetworkDespawn() { Debug.LogException(e); } + + // Deinitialize all NetworkVariables in the event the associated + // NetworkObject is recylced (in-scene placed or pooled). + for (int i = 0; i < NetworkVariableFields.Count; i++) + { + NetworkVariableFields[i].Deinitialize(); + } } /// @@ -918,10 +925,30 @@ internal void __nameNetworkVariable(NetworkVariableBase variable, string varName variable.Name = varName; } + /// + /// Does a first pass initialization for RPCs and NetworkVariables + /// If already initialized, then it just re-initializes the NetworkVariables. + /// internal void InitializeVariables() { if (m_VarInit) { + // If the primary initialization has already been done, then go ahead + // and re-initialize each NetworkVariable in the event it is an in-scene + // placed NetworkObject in an already loaded scene that has already been + // used within a network session =or= if this is a pooled NetworkObject + // that is being repurposed. + for (int i = 0; i < NetworkVariableFields.Count; i++) + { + // If already initialized, then skip + if (NetworkVariableFields[i].HasBeenInitialized) + { + continue; + } + NetworkVariableFields[i].Initialize(this); + } + // Exit early as we don't need to run through the rest of this initialization + // process return; } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 537d3eba38..66a5b8f955 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -35,6 +35,12 @@ public abstract class NetworkVariableBase : IDisposable private NetworkManager m_InternalNetworkManager; + // Determines if this NetworkVariable has been "initialized" to prevent initializing more than once which can happen when first + // instantiated and spawned. If this NetworkVariable instance is on an in-scene placed NetworkObject =or= a pooled NetworkObject + // that can persist between sessions and/or be recycled we need to reset the LastUpdateSent value prior to spawning otherwise + // this NetworkVariableBase property instance will not update until the last session time used. + internal bool HasBeenInitialized { get; private set; } + internal string GetWritePermissionError() { return $"|Client-{m_NetworkManager.LocalClientId}|{m_NetworkBehaviour.name}|{Name}| Write permissions ({WritePerm}) for this client instance is not allowed!"; @@ -45,17 +51,7 @@ internal void LogWritePermissionError() Debug.LogError(GetWritePermissionError()); } - private protected NetworkManager m_NetworkManager - { - get - { - if (m_InternalNetworkManager == null && m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager) - { - m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; - } - return m_InternalNetworkManager; - } - } + private protected NetworkManager m_NetworkManager => m_InternalNetworkManager; public NetworkBehaviour GetBehaviour() { @@ -68,21 +64,77 @@ public NetworkBehaviour GetBehaviour() /// The NetworkBehaviour the NetworkVariable belongs to public void Initialize(NetworkBehaviour networkBehaviour) { - m_InternalNetworkManager = null; + // If we have already been initialized, then exit early. + // This can happen on the very first instantiation and spawning of the associated NetworkObject + if (HasBeenInitialized) + { + return; + } + + // Throw an exception if there is an invalid NetworkBehaviour parameter + if (!networkBehaviour) + { + throw new Exception($"[{GetType().Name}][Initialize] {nameof(NetworkBehaviour)} parameter passed in is null!"); + } m_NetworkBehaviour = networkBehaviour; - if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager) + + // Throw an exception if there is no NetworkManager available + if (!m_NetworkBehaviour.NetworkManager) { - m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager; - // When in distributed authority mode, there is no such thing as server write permissions - InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; + // Exit early if there has yet to be a NetworkManager assigned. + // This is ok because Initialize is invoked multiple times until + // it is considered "initialized". + return; + } - if (m_NetworkBehaviour.NetworkManager.NetworkTimeSystem != null) - { - UpdateLastSentTime(); - } + if (!m_NetworkBehaviour.NetworkObject) + { + // Exit early if there has yet to be a NetworkObject assigned. + // This is ok because Initialize is invoked multiple times until + // it is considered "initialized". + return; + } + + if (!m_NetworkBehaviour.NetworkObject.NetworkManagerOwner) + { + // Exit early if there has yet to be a NetworkManagerOwner assigned + // to the NetworkObject. This is ok because Initialize is invoked + // multiple times until it is considered "initialized". + return; } + m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject.NetworkManagerOwner; + + // When in distributed authority mode, there is no such thing as server write permissions + InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; OnInitialize(); + + // Some unit tests don't operate with a running NetworkManager. + // Only update the last time if there is a NetworkTimeSystem. + if (m_InternalNetworkManager.NetworkTimeSystem != null) + { + // Update our last sent time relative to when this was initialized + UpdateLastSentTime(); + + // At this point, this instance is considered initialized + HasBeenInitialized = true; + } + else if (m_InternalNetworkManager.LogLevel == LogLevel.Developer) + { + Debug.LogWarning($"[{m_NetworkBehaviour.name}][{m_NetworkBehaviour.GetType().Name}][{GetType().Name}][Initialize] {nameof(NetworkManager)} has no {nameof(NetworkTimeSystem)} assigned!"); + } + } + + /// + /// Deinitialize is invoked when a NetworkObject is despawned. + /// This allows for a recyled NetworkObject (in-scene or pooled) + /// to be properly initialized upon the next use/spawn. + /// + internal void Deinitialize() + { + // When despawned, reset the HasBeenInitialized so if the associated NetworkObject instance + // is recylced (i.e. in-scene placed or pooled) it will re-initialize the LastUpdateSent time. + HasBeenInitialized = false; } /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 921d70b928..6222ce7993 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -1821,8 +1821,12 @@ private void UnloadRemainingScenes() private void LogWaitForMessages() { - VerboseDebug(m_WaitForLog.ToString()); - m_WaitForLog.Clear(); + // If there is nothing to log, then don't log anything + if (m_WaitForLog.Length > 0) + { + VerboseDebug(m_WaitForLog.ToString()); + m_WaitForLog.Clear(); + } } private IEnumerator WaitForTickAndFrames(NetworkManager networkManager, int tickCount, float targetFrames) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs new file mode 100644 index 0000000000..545b30456e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs @@ -0,0 +1,412 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + public class NetworkVariableBaseInitializesWhenPersisted : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + private static GameObject s_NetworkPrefab; + + public NetworkVariableBaseInitializesWhenPersisted(NetworkTopologyTypes networkTopologyTypes, HostOrServer hostOrServer) : base(networkTopologyTypes, hostOrServer) { } + + protected override void OnOneTimeSetup() + { + // Create a prefab to persist for all tests + s_NetworkPrefab = new GameObject("PresistPrefab"); + var networkObject = s_NetworkPrefab.AddComponent(); + networkObject.GlobalObjectIdHash = 8888888; + networkObject.SetSceneObjectStatus(false); + s_NetworkPrefab.AddComponent(); + s_NetworkPrefab.AddComponent(); + // Create enough prefab instance handlers to be re-used for all tests. + PrefabInstanceHandler.OneTimeSetup(NumberOfClients + 1, s_NetworkPrefab); + Object.DontDestroyOnLoad(s_NetworkPrefab); + base.OnOneTimeSetup(); + } + + protected override void OnServerAndClientsCreated() + { + // Assign one of the precreated prefab instance handlers to the server + PrefabInstanceHandler.AssignHandler(m_ServerNetworkManager); + // Add the network prefab to the server networkmanager + m_ServerNetworkManager.AddNetworkPrefab(s_NetworkPrefab); + // Repeat these steps for clients + foreach (var client in m_ClientNetworkManagers) + { + PrefabInstanceHandler.AssignHandler(client); + client.AddNetworkPrefab(s_NetworkPrefab); + } + // !!! IMPORTANT !!! + // Disable the persisted network prefab so it isn't spawned nor destroyed + s_NetworkPrefab.SetActive(false); + base.OnServerAndClientsCreated(); + } + + private List m_NetworkManagers = new List(); + private List m_SpawnedObjects = new List(); + + [UnityTest] + public IEnumerator PrefabSessionIstantiationPass([Values(4, 3, 2, 1)] int iterationsLeft) + { + // Start out waiting for a long duration before updating the NetworkVariable so each + // next iteration we change it earlier than the previous. This validates that the + // NetworkVariable's last update time is being reset each time a persisted NetworkObject + // is being spawned. + var baseWaitTime = 0.35f; + var waitPeriod = baseWaitTime * iterationsLeft; + m_NetworkManagers.Add(m_ServerNetworkManager); + m_NetworkManagers.AddRange(m_ClientNetworkManagers); + + foreach (var networkManager in m_NetworkManagers) + { + // Validate Pooled Objects Persisted Between Tests + // We start with having 4 iterations (including the first of the 4), which means that when we have 4 + // iterations remaining we haven't spawned any instances so it will test that there are no instances. + // When we there are 3 iterations left, every persisted instance of the network prefab should have been + // spawned at least once...when 2 then all should have been spawned twice...when 1 then all should been + // spawned three times. + Assert.True(PrefabInstanceHandler.ValidatePersistedInstances(networkManager, 4 - iterationsLeft), "Failed validation of persisted pooled objects!"); + + // Spawn 1 NetworkObject per NetworkManager with the NetworkManager's client being the owner + var networkManagerToSpawn = m_DistributedAuthority ? networkManager : m_ServerNetworkManager; + var objectToSpawn = PrefabInstanceHandler.GetInstanceToSpawn(networkManagerToSpawn); + objectToSpawn.NetworkManagerOwner = networkManagerToSpawn; + objectToSpawn.SpawnWithOwnership(networkManager.LocalClientId); + m_SpawnedObjects.Add(objectToSpawn); + } + + // Conditional wait for all clients to spawn all objects + bool AllInstancesSpawnedOnAllCLients() + { + foreach (var spawnedObject in m_SpawnedObjects) + { + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(spawnedObject.NetworkObjectId)) + { + return false; + } + } + } + return true; + } + + // Wait for all clients to locally clone and spawn all spawned NetworkObjects + yield return WaitForConditionOrTimeOut(AllInstancesSpawnedOnAllCLients); + AssertOnTimeout($"Failed to spawn all instances on all clients!"); + + // Wait for the continually decreasing waitPeriod to validate that + // NetworkVariableBase has reset the last update time. + yield return new WaitForSeconds(waitPeriod); + + // Have the owner of each spawned NetworkObject assign a new NetworkVariable + foreach (var spawnedObject in m_SpawnedObjects) + { + var testBehaviour = spawnedObject.GetComponent(); + if (!m_DistributedAuthority) + { + var client = m_NetworkManagers.Where((c) => c.LocalClientId == testBehaviour.OwnerClientId).First(); + testBehaviour = client.SpawnManager.SpawnedObjects[testBehaviour.NetworkObjectId].GetComponent(); + } + testBehaviour.TestNetworkVariable.Value = Random.Range(0, 1000); + } + + // Wait for half of the base time before checking to see if the time delta is within the acceptable range. + // The time delta is the time from spawn to when the value changes. Each iteration of this test decreases + // the total wait period, and so the delta should always be less than the wait period plus the base wait time. + yield return new WaitForSeconds(baseWaitTime * 0.5f); + + // Conditional to verify the time delta between spawn and the NetworkVariable being updated is within the + // expected range + bool AllSpawnedObjectsUpdatedWithinTimeSpan() + { + foreach (var spawnedObject in m_SpawnedObjects) + { + foreach (var networkManager in m_NetworkManagers) + { + if (spawnedObject.OwnerClientId == networkManager.LocalClientId) + { + continue; + } + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(spawnedObject.NetworkObjectId)) + { + return false; + } + var instance = networkManager.SpawnManager.SpawnedObjects[spawnedObject.NetworkObjectId].GetComponent(); + // Check to make sure all clients' delta between updates is not greater than the wait period plus the baseWaitTime. + // Ignore the first iteration as that becomes our baseline. + if (iterationsLeft < 4 && instance.LastUpdateDelta >= (waitPeriod + baseWaitTime)) + { + VerboseDebug($"Last Spawn Delta = {instance.LastUpdateDelta} is greater or equal to {waitPeriod + baseWaitTime}"); + return false; + } + } + } + return true; + } + + yield return WaitForConditionOrTimeOut(AllSpawnedObjectsUpdatedWithinTimeSpan); + AssertOnTimeout($"Failed to reset NetworkVariableBase instances!"); + } + + + protected override IEnumerator OnTearDown() + { + m_NetworkManagers.Clear(); + m_SpawnedObjects.Clear(); + PrefabInstanceHandler.ReleaseAll(); + yield return base.OnTearDown(); + } + + protected override void OnOneTimeTearDown() + { + Object.DestroyImmediate(s_NetworkPrefab); + s_NetworkPrefab = null; + PrefabInstanceHandler.ReleaseAll(true); + base.OnOneTimeTearDown(); + } + + /// + /// Test NetworkBehaviour that updates a NetworkVariable and tracks the time between + /// spawn and when the NetworkVariable is updated. + /// + public class TestBehaviour : NetworkBehaviour + { + public NetworkVariable TestNetworkVariable = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + public float LastUpdateDelta { get; private set; } + private float m_TimeSinceLastUpdate = 0.0f; + + // How many times has this instance been spawned. + public int SpawnedCount { get; private set; } + + public override void OnNetworkSpawn() + { + SpawnedCount++; + if (IsOwner) + { + TestNetworkVariable.Value = 0; + } + base.OnNetworkSpawn(); + } + + protected override void OnNetworkPostSpawn() + { + if (!IsOwner) + { + TestNetworkVariable.OnValueChanged += OnTestValueChanged; + } + m_TimeSinceLastUpdate = Time.realtimeSinceStartup; + base.OnNetworkPostSpawn(); + } + + public override void OnDestroy() + { + base.OnDestroy(); + } + + public override void OnNetworkDespawn() + { + TestNetworkVariable.OnValueChanged -= OnTestValueChanged; + LastUpdateDelta = 0.0f; + base.OnNetworkDespawn(); + } + + private void OnTestValueChanged(int previous, int current) + { + LastUpdateDelta = Time.realtimeSinceStartup - m_TimeSinceLastUpdate; + } + } + + /// + /// Creates a specified number of instances that persist throughout the entire test session + /// and will only be destroyed/released during the OneTimeTeardown + /// + public class PrefabInstanceHandler : INetworkPrefabInstanceHandler + { + private static Dictionary s_AssignedInstances = new Dictionary(); + private static Queue s_PrefabInstanceHandlers = new Queue(); + public Queue PrefabInstances = new Queue(); + private GameObject m_NetworkPrefab; + private NetworkManager m_NetworkManager; + private ulong m_AssignedClientId; + + public static void OneTimeSetup(int numberOfInstances, GameObject prefabInstance) + { + for (int i = 0; i < numberOfInstances; i++) + { + s_PrefabInstanceHandlers.Enqueue(new PrefabInstanceHandler(prefabInstance)); + } + } + + /// + /// Invoke when s are created but not started. + /// + /// + public static void AssignHandler(NetworkManager networkManager) + { + if (s_PrefabInstanceHandlers.Count > 0) + { + var instance = s_PrefabInstanceHandlers.Dequeue(); + instance.Initialize(networkManager); + s_AssignedInstances.Add(networkManager, instance); + } + else + { + Debug.LogError($"[{nameof(PrefabInstanceHandler)}] Exhausted total number of instances available!"); + } + } + + public static NetworkObject GetInstanceToSpawn(NetworkManager networkManager) + { + if (s_AssignedInstances.ContainsKey(networkManager)) + { + return s_AssignedInstances[networkManager].GetInstance(); + } + return null; + } + + + public static bool ValidatePersistedInstances(NetworkManager networkManager, int minCount) + { + if (s_AssignedInstances.ContainsKey(networkManager)) + { + var prefabInstanceHandler = s_AssignedInstances[networkManager]; + return prefabInstanceHandler.ValidateInstanceSpawnCount(minCount); + } + return false; + } + + /// + /// Releases back to the queue and if destroy is true it will completely + /// remove all references so they are cleaned up when + /// + /// + public static void ReleaseAll(bool destroy = false) + { + foreach (var entry in s_AssignedInstances) + { + entry.Value.DeregisterHandler(); + if (!destroy) + { + s_PrefabInstanceHandlers.Enqueue(entry.Value); + } + else if (entry.Value.PrefabInstances.Count > 0) + { + entry.Value.CleanInstances(); + } + } + s_AssignedInstances.Clear(); + + if (destroy) + { + while (s_PrefabInstanceHandlers.Count > 0) + { + s_PrefabInstanceHandlers.Dequeue(); + } + } + } + + public PrefabInstanceHandler(GameObject gameObject) + { + m_NetworkPrefab = gameObject; + } + + + public void Initialize(NetworkManager networkManager) + { + m_NetworkManager = networkManager; + networkManager.PrefabHandler.AddHandler(m_NetworkPrefab, this); + } + + /// + /// This validates that the instances persisted to the next test set and persisted + /// between network sessions + public bool ValidateInstanceSpawnCount(int minCount) + { + // First pass we should have no instances + if (minCount == 0) + { + return PrefabInstances.Count == 0; + } + else + { + foreach (var instance in PrefabInstances) + { + var testBehaviour = instance.GetComponent(); + if (testBehaviour.SpawnedCount < minCount) + { + return false; + } + } + } + return true; + } + + /// + /// When we are done with all tests, we finally destroy the persisted objects + /// + public void CleanInstances() + { + while (PrefabInstances.Count > 0) + { + var instance = PrefabInstances.Dequeue(); + Object.DestroyImmediate(instance); + } + } + + public void DeregisterHandler() + { + if (m_NetworkManager != null && m_NetworkManager.PrefabHandler != null) + { + m_NetworkManager.PrefabHandler.RemoveHandler(m_NetworkPrefab); + } + } + + public NetworkObject GetInstance() + { + var instanceToReturn = (NetworkObject)null; + if (PrefabInstances.Count == 0) + { + instanceToReturn = Object.Instantiate(m_NetworkPrefab).GetComponent(); + instanceToReturn.SetSceneObjectStatus(false); + instanceToReturn.gameObject.SetActive(true); + } + else + { + instanceToReturn = PrefabInstances.Dequeue().GetComponent(); + instanceToReturn.gameObject.SetActive(true); + SceneManager.MoveGameObjectToScene(instanceToReturn.gameObject, SceneManager.GetActiveScene()); + } + return instanceToReturn; + } + + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + return GetInstance(); + } + + public void Destroy(NetworkObject networkObject) + { + if (m_NetworkPrefab != null && m_NetworkPrefab.gameObject == networkObject.gameObject) + { + return; + } + Object.DontDestroyOnLoad(networkObject.gameObject); + networkObject.gameObject.SetActive(false); + PrefabInstances.Enqueue(networkObject.gameObject); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs.meta new file mode 100644 index 0000000000..160261ff3a --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3f68143136bcd1643a564952c2d48e2c \ No newline at end of file From b6654999c0c04a8476fa4bb3adc395940bb4d20f Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 4 Feb 2025 12:11:40 -0500 Subject: [PATCH 163/236] fix: Unnecessary change ownership message (#3222) --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 9b3ef047a0..5ae8e1d91e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -225,7 +225,7 @@ internal void SetSessionOwner(ulong sessionOwner) foreach (var networkObjectEntry in SpawnManager.SpawnedObjects) { var networkObject = networkObjectEntry.Value; - if (networkObject.IsOwnershipSessionOwner && LocalClient.IsSessionOwner) + if (networkObject.IsOwnershipSessionOwner && networkObject.OwnerClientId != LocalClientId) { SpawnManager.ChangeOwnership(networkObject, LocalClientId, true); } From 2eee998723b04c3994edd1922affa132d7a81b50 Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 5 Feb 2025 07:36:47 -0500 Subject: [PATCH 164/236] fix: NetworkObject.DeferDespawn not respecting DestroyGameObject parameter (#3219) * fix: NetworkObject.DeferDespawn not respecting DestroyGameObject parameter * test-fix Checking to see if the failure was due to the network prefab being spawned when the host-server starts. * test-fix Assure all NetworkManagers are destroyed at the end of TearDown. * Revert "test-fix" This reverts commit 16659e8c60f1b52a23bb2436faca769f351db06f. * test-fix-fix Fixing the bug in my previous fix. * Ensure DeferDespawn tick is always reset * revert DestroyObject message change * Update CHANGELOG.md Adding PR number to changelog entry. --------- Co-authored-by: NoelStephensUnity --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Messages/DestroyObjectMessage.cs | 2 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 10 ++++-- .../Runtime/NetcodeIntegrationTest.cs | 19 ++++++++++ .../DeferredDespawningTests.cs | 35 ++++++++++++++++--- .../InScenePlacedNetworkObjectTests.cs | 24 +++++++++++-- 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 514b6e994b..ef22703a5d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) - Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 868d5a2540..cdcff74251 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -150,7 +150,7 @@ public void Handle(ref NetworkContext context) { networkObject.DeferredDespawnTick = DeferredDespawnTick; var hasCallback = networkObject.OnDeferredDespawnComplete != null; - networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback); + networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback, DestroyGameObject); return; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 854bc8fba2..beba9c7055 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1450,7 +1450,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep() } // Since we are spawing in-scene placed NetworkObjects for already loaded scenes, - // we need to add any in-scene placed NetworkObject to our tracking table + // we need to add any in-scene placed NetworkObject to our tracking table var clearFirst = true; foreach (var sceneLoaded in NetworkManager.SceneManager.ScenesLoaded) { @@ -1588,6 +1588,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec { NetworkObjectId = networkObject.NetworkObjectId, DeferredDespawnTick = networkObject.DeferredDespawnTick, + // DANGO-TODO: Reconfigure Client-side object destruction on despawn DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true, IsTargetedDestroy = false, IsDistributedAuthority = distributedAuthority, @@ -1601,6 +1602,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } networkObject.IsSpawned = false; + networkObject.DeferredDespawnTick = 0; if (SpawnedObjects.Remove(networkObject.NetworkObjectId)) { @@ -1920,6 +1922,7 @@ internal struct DeferredDespawnObject { public int TickToDespawn; public bool HasDeferredDespawnCheck; + public bool DestroyGameObject; public ulong NetworkObjectId; } @@ -1931,12 +1934,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); @@ -2001,7 +2005,7 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) } var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId]; // Local instance despawns the instance - OnDespawnObject(networkObject, true); + OnDespawnObject(networkObject, deferredObjectEntry.DestroyGameObject); DeferredDespawnObjects.Remove(deferredObjectEntry); } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 6222ce7993..6a58cea9a7 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -1156,6 +1156,9 @@ protected void ShutdownAndCleanUp() // reset the m_ServerWaitForTick for the next test to initialize s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate); VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}"); + + // Assure any remaining NetworkManagers are destroyed + DestroyNetworkManagers(); } protected IEnumerator CoroutineShutdownAndCleanUp() @@ -1195,6 +1198,9 @@ protected IEnumerator CoroutineShutdownAndCleanUp() // reset the m_ServerWaitForTick for the next test to initialize s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate); VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}"); + + // Assure any remaining NetworkManagers are destroyed + DestroyNetworkManagers(); } /// @@ -1244,6 +1250,19 @@ public IEnumerator TearDown() VerboseDebug($"Exiting {nameof(TearDown)}"); LogWaitForMessages(); NetcodeLogAssert.Dispose(); + + } + + /// + /// Destroys any remaining NetworkManager instances + /// + private void DestroyNetworkManagers() + { + var networkManagers = Object.FindObjectsByType(FindObjectsSortMode.None); + foreach (var networkManager in networkManagers) + { + Object.DestroyImmediate(networkManager.gameObject); + } } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs index faba405079..5787f169f1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs @@ -9,6 +9,8 @@ namespace Unity.Netcode.RuntimeTests { + [TestFixture(SetDestroyGameObject.DestroyGameObject)] + [TestFixture(SetDestroyGameObject.DontDestroyGameObject)] internal class DeferredDespawningTests : IntegrationTestWithApproximation { private const int k_DaisyChainedCount = 5; @@ -16,8 +18,16 @@ internal class DeferredDespawningTests : IntegrationTestWithApproximation private List m_DaisyChainedDespawnObjects = new List(); private List m_HasReachedEnd = new List(); - public DeferredDespawningTests() : base(HostOrServer.DAHost) + public enum SetDestroyGameObject { + DestroyGameObject, + DontDestroyGameObject, + } + private bool m_DestroyGameObject; + + public DeferredDespawningTests(SetDestroyGameObject destroyGameObject) : base(HostOrServer.DAHost) + { + m_DestroyGameObject = destroyGameObject == SetDestroyGameObject.DestroyGameObject; } protected override void OnServerAndClientsCreated() @@ -45,12 +55,28 @@ protected override void OnServerAndClientsCreated() [UnityTest] public IEnumerator DeferredDespawning() { + // Setup for test + DeferredDespawnDaisyChained.DestroyGameObject = m_DestroyGameObject; DeferredDespawnDaisyChained.EnableVerbose = m_EnableVerboseDebug; + DeferredDespawnDaisyChained.ClientRelativeInstances = new Dictionary>(); + + // Spawn the initial object var rootInstance = SpawnObject(m_DaisyChainedDespawnObjects[0], m_ServerNetworkManager); DeferredDespawnDaisyChained.ReachedLastChainInstance = ReachedLastChainObject; + + // Wait for the chain of objects to spawn and despawn var timeoutHelper = new TimeoutHelper(300); yield return WaitForConditionOrTimeOut(HaveAllClientsReachedEndOfChain, timeoutHelper); AssertOnTimeout($"Timed out waiting for all children to reach the end of their chained deferred despawns!", timeoutHelper); + + if (m_DestroyGameObject) + { + Assert.IsTrue(rootInstance == null); // Assert.IsNull doesn't work here + } + else + { + Assert.IsTrue(rootInstance != null); // Assert.IsNotNull doesn't work here + } } private bool HaveAllClientsReachedEndOfChain() @@ -88,9 +114,10 @@ private void ReachedLastChainObject(ulong clientId) internal class DeferredDespawnDaisyChained : NetworkBehaviour { public static bool EnableVerbose; + public static bool DestroyGameObject; public static Action ReachedLastChainInstance; private const int k_StartingDeferTick = 4; - public static Dictionary> ClientRelativeInstances = new Dictionary>(); + public static Dictionary> ClientRelativeInstances; public bool IsRoot; public GameObject PrefabToSpawnWhenDespawned; public bool WasContactedByPeviousChainMember { get; private set; } @@ -182,7 +209,7 @@ private void InvokeDespawn() { FailTest($"[{nameof(InvokeDespawn)}] Client is not the authority but this was invoked (integration test logic issue)!"); } - NetworkObject.DeferDespawn(DeferDespawnTick); + NetworkObject.DeferDespawn(DeferDespawnTick, DestroyGameObject); } public override void OnDeferringDespawn(int despawnTick) @@ -241,7 +268,7 @@ private void Update() continue; } - // This should happen shortly afte the instances spawns (based on the deferred despawn count) + // This should happen shortly after the instances spawn (based on the deferred despawn count) if (!IsRoot && !ClientRelativeInstances[clientId][NetworkObjectId].WasContactedByPeviousChainMember) { // exit early if the non-authority instance has not been contacted yet diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index 59922c5959..f0def620ff 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -53,6 +53,12 @@ protected override bool CanStartServerAndClients() return m_CanStartServerAndClients; } + public enum DespawnMode + { + Despawn, + DeferDespawn, + } + /// /// This verifies that in-scene placed NetworkObjects will be properly /// synchronized if: @@ -61,8 +67,13 @@ protected override bool CanStartServerAndClients() /// NetworkObject as a NetworkPrefab /// [UnityTest] - public IEnumerator InSceneNetworkObjectSynchAndSpawn() + 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."); + } + NetworkObjectTestComponent.VerboseDebug = true; // Because despawning a client will cause it to shutdown and clean everything in the // scene hierarchy, we have to prevent one of the clients from spawning initially before @@ -99,7 +110,16 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn() // Despawn the in-scene placed NetworkObject Debug.Log("Despawning In-Scene placed NetworkObject"); - serverObject.Despawn(false); + + 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()}"); From 05c64fad36b95ea72586ed142cf96b3c3c427f58 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 5 Feb 2025 10:44:27 -0600 Subject: [PATCH 165/236] chore: remove unitytransport debug asserts [up-port] (#3226) * update Replace the two places where Debug.Assert is used in UnityTransport to be DEBUG wrapped checks that log warnings as opposed to throwing asserts in order to avoid being considered a failed test run even if all tests have passed. * style Cleaning up message a bit. * test update to account for switching from a Debug.Assert to a Debug.LogWarning. --- .../Runtime/Transports/UTP/UnityTransport.cs | 16 ++++++++++++++-- .../Runtime/Transports/UnityTransportTests.cs | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 634d544c84..c3cd824489 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1220,7 +1220,13 @@ public override void DisconnectLocalClient() /// The client to disconnect public override void DisconnectRemoteClient(ulong clientId) { - Debug.Assert(m_State == State.Listening, "DisconnectRemoteClient should be called on a listening server"); +#if DEBUG + if (m_State != State.Listening) + { + Debug.LogWarning($"{nameof(DisconnectRemoteClient)} should only be called on a listening server!"); + return; + } +#endif if (m_State == State.Listening) { @@ -1292,7 +1298,13 @@ public NetworkEndpoint GetEndpoint(ulong clientId) /// The NetworkManager that initialized and owns the transport public override void Initialize(NetworkManager networkManager = null) { - Debug.Assert(sizeof(ulong) == UnsafeUtility.SizeOf(), "Netcode connection id size does not match UTP connection id size"); +#if DEBUG + if (sizeof(ulong) != UnsafeUtility.SizeOf()) + { + Debug.LogWarning($"Netcode connection id size {sizeof(ulong)} does not match UTP connection id size {UnsafeUtility.SizeOf()}!"); + return; + } +#endif m_NetworkManager = networkManager; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index 016d45746c..711a4f3bef 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -531,7 +531,7 @@ public IEnumerator DoesNotActAfterShutdown([Values] AfterShutdownAction afterShu { m_Server.DisconnectRemoteClient(m_Client1.ServerClientId); - LogAssert.Expect(LogType.Assert, "DisconnectRemoteClient should be called on a listening server"); + LogAssert.Expect(LogType.Warning, $"{nameof(UnityTransport.DisconnectRemoteClient)} should only be called on a listening server!"); } else if (afterShutdownAction == AfterShutdownAction.DisconnectLocalClient) { From 6a4f718acf9eb4eed0ef304b49355ded28d97a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:21:52 +0100 Subject: [PATCH 166/236] ci: enabling renovate bot on develop-2.0.0 (#3191) * Renovate setup files * Correcting cron and comment --------- Co-authored-by: NoelStephensUnity --- .github/workflows/renovate-validation.yml | 38 +++++++++++++++++++++++ .github/workflows/renovate.yml | 33 ++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 .github/workflows/renovate-validation.yml create mode 100644 .github/workflows/renovate.yml diff --git a/.github/workflows/renovate-validation.yml b/.github/workflows/renovate-validation.yml new file mode 100644 index 0000000000..9ef821bcbd --- /dev/null +++ b/.github/workflows/renovate-validation.yml @@ -0,0 +1,38 @@ +# This workflow is for validating the Renovate configuration and docker image +# updates for it. +name: Renovate Validation +on: + workflow_dispatch: + inputs: + log-level: + type: choice + description: Select log level for Renovate + options: + - trace + - debug + - info + - warn + - error + default: info + required: false + pull_request: + paths: + # we trigger validation on any changes to the renovate workflow files + - .github/workflows/renovate*.yml + # as well as for any possible location for the renovate config file + - .github/renovate.json? + + +jobs: + renovate-validation: + # The reusable workflow will be updated by renovate if there's a new version + uses: Unity-Technologies/renovate-workflows/.github/workflows/run.yml@v5.0.0 + with: + # This is the image that contains our custom renovate and will be auto + # updated by Renovate itself. + image: europe-docker.pkg.dev/unity-cds-services-prd/ds-docker/renovate:10.1.3@sha256:fdeed7bb524bd67611eb91ee1a5e990c8c73ed62c84a0cd5ef66c87eb5fd0d70 + dry-run: full + log-level: ${{ github.event.inputs.log-level }} + secrets: + renovate-auth-secret: ${{ secrets.RENOVATE_AUTH_SECRET }} + github-com-token: ${{ secrets.GH_COM_TOKEN }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000000..f859c46a94 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,33 @@ +# This workflow runs Renovate against the current repo and will create PRs with outdated dependencies. +name: Renovate + +on: + workflow_dispatch: + inputs: + log-level: + type: choice + description: Select log level for Renovate + options: + - trace + - debug + - info + - warn + - error + default: info + required: false + schedule: + # Every 6 hours at the 6th minute. + - cron: '06 */6 * * *' + +jobs: + renovate: + # The reusable workflow will be updated by renovate if there's a new version + uses: Unity-Technologies/renovate-workflows/.github/workflows/run.yml@v5.0.0 + with: + # This is the image that contains our custom renovate and will be auto + # updated by Renovate itself. + image: europe-docker.pkg.dev/unity-cds-services-prd/ds-docker/renovate:10.1.3@sha256:fdeed7bb524bd67611eb91ee1a5e990c8c73ed62c84a0cd5ef66c87eb5fd0d70 + log-level: ${{ github.event.inputs.log-level }} + secrets: + renovate-auth-secret: ${{ secrets.RENOVATE_AUTH_SECRET }} + github-com-token: ${{ secrets.GH_COM_TOKEN }} \ No newline at end of file From cd6fd5b2fe8c1a287dd724e88b6d38c5dada76c5 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 7 Feb 2025 18:22:33 -0500 Subject: [PATCH 167/236] fix: Exception with disabled GameObjects and NetworkTransforms (#3243) * fix: Exception with disabled GameObjects and NetworkTransforms * Add CHANGELOG * Update naming and code comments to be clearer * fix Minor adjustment as it looks like DAHost might not have been properly forwarding messages of NetworkTransformMessages when the NetworkObject was hidden from it. * fix Some minor tweaks for extracting the reliability used. * style updating the test LogAssert.Expect string used. * update + style Just keeping the established pattern of using "nameof" within a string. Also added the NetworkTransform's NetworkBehaviourId value as opposed to hard coding the value. (minor tweaks) --------- Co-authored-by: NoelStephensUnity --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Messages/NetworkTransformMessage.cs | 43 ++++-- .../NetworkTransformErrorTests.cs | 140 ++++++++++++++++++ .../NetworkTransformErrorTests.cs.meta | 3 + 4 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ef22703a5d..12accde27a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) - Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs index cf4013f469..cb01fcf7b4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs @@ -71,8 +71,21 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int if (isSpawnedLocally) { networkObject = networkManager.SpawnManager.SpawnedObjects[networkObjectId]; + if (networkObject.ChildNetworkBehaviours.Count <= networkBehaviourId || networkObject.ChildNetworkBehaviours[networkBehaviourId] == null) + { + Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Targeted {nameof(NetworkTransform)}, {nameof(NetworkBehaviour.NetworkBehaviourId)} ({networkBehaviourId}), does not exist! Make sure you are not spawning {nameof(NetworkObject)}s with disabled {nameof(GameObject)}s that have {nameof(NetworkBehaviour)} components on them."); + return false; + } + // Get the target NetworkTransform - NetworkTransform = networkObject.ChildNetworkBehaviours[networkBehaviourId] as NetworkTransform; + var transform = networkObject.ChildNetworkBehaviours[networkBehaviourId] as NetworkTransform; + if (transform == null) + { + Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Targeted {nameof(NetworkTransform)}, {nameof(NetworkBehaviour.NetworkBehaviourId)} ({networkBehaviourId}), does not exist! Make sure you are not spawning {nameof(NetworkObject)}s with disabled {nameof(GameObject)}s that have {nameof(NetworkBehaviour)} components on them."); + return false; + } + + NetworkTransform = transform; isServerAuthoritative = NetworkTransform.IsServerAuthoritative(); ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer; @@ -81,8 +94,20 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } else { - // Deserialize the state - reader.ReadNetworkSerializableInPlace(ref State); + ownerAuthoritativeServerSide = networkManager.DAHost; + // If we are the DAHost and the NetworkObject is hidden from the host we still need to forward this message. + if (ownerAuthoritativeServerSide) + { + // We need to deserialize the state to our local State property so we can extract the reliability used. + reader.ReadNetworkSerializableInPlace(ref State); + // Fall through to act like a proxy for this message. + } + else + { + // Otherwise we can error out because we either shouldn't be receiving this message. + Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Target NetworkObject ({networkObjectId}) does not exist!"); + return false; + } } unsafe @@ -106,12 +131,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ByteUnpacker.ReadValueBitPacked(reader, out targetId); targetIds[i] = targetId; } - - if (!isSpawnedLocally) - { - // If we are the DAHost and the NetworkObject is hidden from the host we still need to forward this message - ownerAuthoritativeServerSide = networkManager.DAHost && !isSpawnedLocally; - } } var ownerClientId = (ulong)0; @@ -132,7 +151,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ownerClientId = context.SenderId; } - var networkDelivery = State.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; + // Depending upon whether it is spawned locally or not, get the deserialized state + var stateToUse = NetworkTransform != null ? NetworkTransform.InboundState : State; + // Determine the reliability used to send the message + var networkDelivery = stateToUse.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced; // Forward the state update if there are any remote clients to foward it to if (networkManager.ConnectionManager.ConnectedClientsList.Count > (networkManager.IsHost ? 2 : 1)) @@ -160,7 +182,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int { continue; } - networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId); } // Dispose of the reader used for forwarding diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs new file mode 100644 index 0000000000..d70984062e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs @@ -0,0 +1,140 @@ +using System.Collections; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + + +namespace Unity.Netcode.RuntimeTests +{ + internal class NetworkTransformErrorTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + private GameObject m_AuthorityPrefab; + private GameObject m_NonAuthorityPrefab; + + private HostAndClientPrefabHandler m_HostAndClientPrefabHandler; + + public class EmptyNetworkBehaviour : NetworkBehaviour { } + + /// + /// PrefabHandler that tracks and separates the client GameObject from the host GameObject. + /// Allows independent management of client and host game world while still instantiating NetworkObjects as expected. + /// + private class HostAndClientPrefabHandler : INetworkPrefabInstanceHandler + { + /// + /// The registered prefab is the prefab the networking stack is instantiated with. + /// Registering the prefab simulates the prefab that exists on the authority. + /// + private readonly GameObject m_RegisteredPrefab; + + /// + /// Mocks the registered prefab changing on the non-authority after registration. + /// Allows testing situations mismatched GameObject state between the authority and non-authority. + /// + private readonly GameObject m_InstantiatedPrefab; + + public HostAndClientPrefabHandler(GameObject authorityPrefab, GameObject nonAuthorityPrefab) + { + m_RegisteredPrefab = authorityPrefab; + m_InstantiatedPrefab = nonAuthorityPrefab; + } + + /// + /// Returns the prefab that will mock the instantiated prefab not matching the registered prefab + /// + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + return Object.Instantiate(m_InstantiatedPrefab).GetComponent(); + } + + public void Destroy(NetworkObject networkObject) + { + Object.Destroy(networkObject.gameObject); + } + + public void Register(NetworkManager networkManager) + { + // Register the version that will be spawned by the authority (i.e. Host) + networkManager.PrefabHandler.AddHandler(m_RegisteredPrefab, this); + } + } + + /// + /// Creates a GameObject and sets the transform parent to the given transform + /// Adds a component of the given type to the GameObject + /// + private static void AddChildToNetworkObject(Transform transform) where T : Component + { + var gameObj = new GameObject(); + gameObj.transform.parent = transform; + gameObj.AddComponent(); + } + + protected override void OnServerAndClientsCreated() + { + // Create a prefab that has many child NetworkBehaviours + m_AuthorityPrefab = CreateNetworkObjectPrefab("AuthorityPrefab"); + AddChildToNetworkObject(m_AuthorityPrefab.transform); + AddChildToNetworkObject(m_AuthorityPrefab.transform); + AddChildToNetworkObject(m_AuthorityPrefab.transform); + + // Create a second prefab with only one NetworkBehaviour + // This simulates the GameObjects on the other NetworkBehaviours being disabled + m_NonAuthorityPrefab = CreateNetworkObjectPrefab("NonAuthorityPrefab"); + AddChildToNetworkObject(m_NonAuthorityPrefab.transform); + + // Create and register a prefab handler + // The prefab handler will behave as if the GameObjects have been disabled on the non-authority client + m_HostAndClientPrefabHandler = new HostAndClientPrefabHandler(m_AuthorityPrefab, m_NonAuthorityPrefab); + m_HostAndClientPrefabHandler.Register(m_ServerNetworkManager); + foreach (var client in m_ClientNetworkManagers) + { + m_HostAndClientPrefabHandler.Register(client); + } + + base.OnServerAndClientsCreated(); + } + + /// + /// Validates the fix where would throw an exception + /// if a user sets a with one or more components + /// to inactive. + /// + [UnityTest] + public IEnumerator DisabledGameObjectErrorTest() + { + var instance = SpawnObject(m_AuthorityPrefab, m_ServerNetworkManager); + var networkObjectInstance = instance.GetComponent(); + var networkTransformInstance = instance.GetComponentInChildren(); + + yield return WaitForConditionOrTimeOut(() => ObjectSpawnedOnAllClients(networkObjectInstance.NetworkObjectId)); + AssertOnTimeout("Timed out waiting for object to spawn!"); + + var errorMessage = $"[Netcode] {nameof(NetworkBehaviour)} index {networkTransformInstance.NetworkBehaviourId} was out of bounds for {m_NonAuthorityPrefab.name}(Clone). " + + $"{nameof(NetworkBehaviour)}s must be the same, and in the same order, between server and client."; + LogAssert.Expect(LogType.Error, errorMessage); + errorMessage = $"[{nameof(NetworkTransformMessage)}][Invalid] Targeted {nameof(NetworkTransform)}, {nameof(NetworkBehaviour.NetworkBehaviourId)} " + + $"({networkTransformInstance.NetworkBehaviourId}), does not exist! Make sure you are not spawning {nameof(NetworkObject)}s with disabled {nameof(GameObject)}s that have " + + $"{nameof(NetworkBehaviour)} components on them."; + LogAssert.Expect(LogType.Error, errorMessage); + + yield return new WaitForSeconds(0.3f); + } + + private bool ObjectSpawnedOnAllClients(ulong networkObjectId) + { + foreach (var client in m_ClientNetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + return false; + } + } + return true; + } + } + +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs.meta new file mode 100644 index 0000000000..b99b07b5a5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformErrorTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9f0f7bfcdffd47139aae1788ee8d2063 +timeCreated: 1738881698 \ No newline at end of file From 21b7023422e6c8de3938bc0ab86e6c57875ff422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:28:02 +0100 Subject: [PATCH 168/236] chore: [Backport] Added Ci related folders to gitignore and updated Wrench (#3282) * chore: Added Ci related folders to gitignore and updated Wrench (#3281) * Updated Gitignore files * Updated Wrench version as well as TargetFramework from net7.0 to net8.0 * Regenerated wrench recipes * Regenerated wrench config --- .gitignore | 2 + .yamato/wrench/api-validation-jobs.yml | 10 +- .yamato/wrench/package-pack-jobs.yml | 12 +- .yamato/wrench/preview-a-p-v.yml | 233 +++++++++++++++++--- .yamato/wrench/promotion-jobs.yml | 66 +++++- .yamato/wrench/recipe-regeneration.yml | 8 +- .yamato/wrench/validation-jobs.yml | 291 ++++++++++++++++++++++--- .yamato/wrench/wrench_config.json | 4 +- Tools/CI/NGO.Cookbook.csproj | 4 +- 9 files changed, 536 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index 4b4867581e..1d3ee28bec 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ upm-ci~ utr utr.bat .download +Tools/CI/bin +Tools/CI/obj diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 03dbad8669..89fd3b31d8 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -1,5 +1,5 @@ # Auto-generated by Recipe Engine, do not modify manually. -# This job is generated by the wrench recipe engine module. +# This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb # upm-ci validation tests for API Validation - netcode.gameobjects - 6000.0 - windows (6000.0 - Windows). api_validation_-_netcode_gameobjects_-_6000_0_-_windows: @@ -9,7 +9,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -30,7 +30,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: retries: 0 after: - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" timeout: 10 retries: 1 artifacts: @@ -50,11 +50,11 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 triggers: expression: push.branch match "^release/.*" cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 0dfb1e44cf..6c51944ed4 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -1,7 +1,7 @@ # Auto-generated by Recipe Engine, do not modify manually. -# This job is generated by the wrench recipe engine module. +# This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb -# Pack and Sign Netcode for GameObjects +# Pack Netcode for GameObjects package_pack_-_netcode_gameobjects: name: Package Pack - netcode.gameobjects agent: @@ -12,7 +12,7 @@ package_pack_-_netcode_gameobjects: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -20,7 +20,7 @@ package_pack_-_netcode_gameobjects: - command: cp upm-ci~/packages/packages.json upm-ci~/packages/com.unity.netcode.gameobjects_packages.json after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -29,8 +29,8 @@ package_pack_-_netcode_gameobjects: - upm-ci~/packages/**/* variables: UPMCI_ACK_LARGE_PACKAGE: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 9ad7ec7b24..b9a00ccacc 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -1,5 +1,5 @@ # Auto-generated by Recipe Engine, do not modify manually. -# This job is generated by the wrench recipe engine module. +# This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb # Parent Preview APV Job. all_preview_apv_jobs: @@ -11,12 +11,15 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_1_-_macos - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_1_-_ubuntu - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_1_-_windows + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_macos + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_ubuntu + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows triggers: expression: push.branch match "^release/.*" cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -26,7 +29,7 @@ preview_apv_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -40,7 +43,7 @@ preview_apv_-_6000_0_-_macos: - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -70,10 +73,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -83,7 +86,7 @@ preview_apv_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -97,7 +100,7 @@ preview_apv_-_6000_0_-_ubuntu: - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -127,10 +130,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -140,8 +143,8 @@ preview_apv_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -155,7 +158,7 @@ preview_apv_-_6000_0_-_windows: - command: python PythonScripts/editor_manifest_validator.py --version=6000.0 --wrench-config=.yamato/wrench/wrench_config.json after: - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" timeout: 10 retries: 1 artifacts: @@ -185,11 +188,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 - interpreter: powershell + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -199,7 +201,7 @@ preview_apv_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -213,7 +215,7 @@ preview_apv_-_6000_1_-_macos: - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -243,10 +245,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -256,7 +258,7 @@ preview_apv_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -270,7 +272,7 @@ preview_apv_-_6000_1_-_ubuntu: - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -300,10 +302,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -313,8 +315,8 @@ preview_apv_-_6000_1_-_windows: type: Unity::VM flavor: b1.large commands: - - command: reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -328,7 +330,7 @@ preview_apv_-_6000_1_-_windows: - command: python PythonScripts/editor_manifest_validator.py --version=6000.1 --wrench-config=.yamato/wrench/wrench_config.json after: - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" timeout: 10 retries: 1 artifacts: @@ -358,9 +360,180 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 - interpreter: powershell + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 + +# Functional tests for dependents found in the latest 6000.2 manifest (MacOS). +preview_apv_-_6000_2_-_macos: + name: Preview APV - 6000.2 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.2 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.2 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.36.0 + +# Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). +preview_apv_-_6000_2_-_ubuntu: + name: Preview APV - 6000.2 - ubuntu + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.2 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.2 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.36.0 + +# Functional tests for dependents found in the latest 6000.2 manifest (Windows). +preview_apv_-_6000_2_-_windows: + name: Preview APV - 6000.2 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + timeout: 20 + retries: 10 + - command: unity-downloader-cli -u 6000.2 -c Editor --fast + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.2 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.2 --wrench-config=.yamato/wrench/wrench_config.json + after: + - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + logs: + paths: + - '*.log' + - '*.xml' + - upm-ci~/test-results/**/* + - upm-ci~/temp/*/Logs/** + - upm-ci~/temp/*/Library/*.log + - upm-ci~/temp/*/*.log + - upm-ci~/temp/Builds/*.log + packages: + paths: + - upm-ci~/packages/**/* + PreviewAPVResults: + paths: + - PreviewApvArtifacts~/** + - APVTest/**/manifest.json + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.36.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index d3f5d202d2..71e0b276e1 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -1,5 +1,5 @@ # Auto-generated by Recipe Engine, do not modify manually. -# This job is generated by the wrench recipe engine module. +# This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb # Publish Dry Run for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_dry_run_netcode_gameobjects: @@ -9,7 +9,7 @@ publish_dry_run_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -19,7 +19,7 @@ publish_dry_run_netcode_gameobjects: - command: python PythonScripts/run_publish_if_any_package_left.py --dry-run after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -77,15 +77,39 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-windows unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_macos + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.2-macos + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.2-macos + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_ubuntu + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.2-ubuntu + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.2-ubuntu + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_windows + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.2-windows + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.2-windows + unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 triggers: expression: push.branch match "^release/.*" cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -95,7 +119,7 @@ publish_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -105,7 +129,7 @@ publish_netcode_gameobjects: - command: python PythonScripts/run_publish_if_any_package_left.py after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -163,10 +187,34 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-windows unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_macos + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.2-macos + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.2-macos + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_ubuntu + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.2-ubuntu + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.2-ubuntu + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_windows + specific_options: + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.2-windows + unzip: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.2-windows + unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index 03a579082d..2998d6cb5d 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -1,5 +1,5 @@ # Auto-generated by Recipe Engine, do not modify manually. -# This job is generated by the wrench recipe engine module. +# This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb # Test that Generated Wrench Jobs are up to date. test_-_wrench_jobs_up_to_date: @@ -9,14 +9,14 @@ test_-_wrench_jobs_up_to_date: type: Unity::VM flavor: b1.large commands: - - command: dotnet run --project Tools\CI\NGO.Cookbook.csproj + - command: dotnet run --project Tools/CI/NGO.Cookbook.csproj - command: |- if [ -n "$(git status --porcelain)" ]; then git status echo "Your repo is not clean - diff output:" git diff echo "You must run recipe generation after updating recipes to update the generated YAML!" - echo "Run 'dotnet run --project Tools\CI\NGO.Cookbook.csproj' from the root of your repository to regenerate all job definitions created by wrench." + echo "Run 'dotnet run --project Tools/CI/NGO.Cookbook.csproj' from the root of your repository to regenerate all job definitions created by wrench." exit 1 fi variables: @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index db48d0b2bc..fa10d4953d 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -9,7 +9,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -23,16 +23,18 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 20 retries: 0 - - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 30 retries: 1 after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -61,10 +63,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 labels: - Packages:netcode.gameobjects @@ -76,7 +78,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -90,16 +92,18 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 20 retries: 0 - - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 30 retries: 1 after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -128,10 +132,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 labels: - Packages:netcode.gameobjects @@ -143,7 +147,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -157,16 +161,18 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 20 retries: 0 - - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 30 retries: 1 after: - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" timeout: 10 retries: 1 artifacts: @@ -195,10 +201,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 labels: - Packages:netcode.gameobjects @@ -210,7 +216,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -224,16 +230,18 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 20 retries: 0 - - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 30 retries: 1 after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -262,10 +270,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 labels: - Packages:netcode.gameobjects @@ -277,7 +285,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -291,16 +299,18 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 20 retries: 0 - - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 30 retries: 1 after: - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.0 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 timeout: 10 retries: 1 artifacts: @@ -329,10 +339,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 labels: - Packages:netcode.gameobjects @@ -344,7 +354,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-54_4ed3929f39f0279ecefec81a437d0eb42e21a4f9c6469a88d31e8f3d764709fe.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -358,16 +368,225 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 20 retries: 0 - - command: echo Skipping check for PVP-160-1 as there is a bug on Windows. https://jira.unity3d.com/browse/PETS-1462 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.36.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.2 - macos (6000.2 - MacOS). +validate_-_netcode_gameobjects_-_6000_2_-_macos: + name: Validate - netcode.gameobjects - 6000.2 - macos + agent: + image: package-ci/macos-13:default + type: Unity::VM::osx + flavor: b1.xlarge + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.2 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.36.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.2 - ubuntu (6000.2 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: + name: Validate - netcode.gameobjects - 6000.2 - ubuntu + agent: + image: package-ci/ubuntu-20.04:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.2 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 + - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 10 + retries: 0 + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + timeout: 30 + retries: 1 + after: + - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 + - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 + timeout: 10 + retries: 1 + artifacts: + Crash Dumps: + paths: + - CrashDumps/** + packages: + paths: + - upm-ci~/packages/**/* + pvp-results: + paths: + - upm-ci~/pvp/**/* + browsable: onDemand + UTR: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - test-netcode.gameobjects/Logs/** + - test-netcode.gameobjects/Library/*.log + - test-netcode.gameobjects/*.log + - test-netcode.gameobjects/Builds/*.log + - build/test-results/** + browsable: onDemand + dependencies: + - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects + variables: + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 0.10.36.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.2 - windows (6000.2 - Windows). +validate_-_netcode_gameobjects_-_6000_2_-_windows: + name: Validate - netcode.gameobjects - 6000.2 - windows + agent: + image: package-ci/win10:default + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: 7z x -aoa wrench-localapv.zip + - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple + - command: python PythonScripts/print_machine_info.py + - command: unity-downloader-cli -u 6000.2 -c Editor --fast + timeout: 10 + retries: 3 + - command: upm-pvp create-test-project test-netcode.gameobjects --packages "upm-ci~/packages/*.tgz" --unity .Editor + timeout: 10 + retries: 1 + - command: echo No internal packages to add. + - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp + timeout: 20 + retries: 0 + - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json + timeout: 5 + retries: 0 - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts_path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 30 retries: 1 after: - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.0 || exit 0" + - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" timeout: 10 retries: 1 artifacts: @@ -396,10 +615,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.31.0 + UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.31.0 + Wrench: 0.10.36.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 2ef87583c3..10b43c396d 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.31.0", + "wrench_version": "0.10.36.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", - "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" + "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index f5b5a6e439..a60fa2f818 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -2,13 +2,13 @@ Exe - net7.0 + net8.0 enable enable - + From c369bfd2f4782bbf36ec3ec3a7d991d575534177 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 10 Feb 2025 15:29:07 -0600 Subject: [PATCH 169/236] fix: distribute parented children distributing with root parent (#3203) * fix This is the initial fix for distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, upon the owning client disconnecting when using a distributed authority network topology. * update Adding initial change log entry. * update adding PR number to entry * update Handle check for parent earlier. Add additional comment details. * fix Adding DAHost side object hierarchy redistribution when a client disconnects. Adding a DA specific work around for integration testing when instantiating and spawning a player within the test runner scene. * test The test to validate these changes using a DAHost. * style Removing white spaces * test Adding ownership locking to the test. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 +- .../Connection/NetworkConnectionManager.cs | 10 +- .../Runtime/Core/NetworkObject.cs | 15 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 45 ++- .../ParentChildDistibutionTests.cs | 291 ++++++++++++++++++ .../ParentChildDistibutionTests.cs.meta | 2 + 6 files changed, 355 insertions(+), 11 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 12accde27a..f63cda27e3 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,12 +14,13 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) - Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219) -- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) +- Fixed issue with distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, were not being distributed to the same client upon the owning client disconnecting when using a distributed authority network topology. (#3203) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) - Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181) ### Changed +- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) ## [2.2.0] - 2024-12-12 diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 06a07acfc4..fd418d34c2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1225,7 +1225,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) } NetworkManager.SpawnManager.ChangeOwnership(ownedObject, targetOwner, true); // DANGO-TODO: Should we try handling inactive NetworkObjects? - // Ownership gets passed down to all children + // Ownership gets passed down to all children that have the same owner. var childNetworkObjects = ownedObject.GetComponentsInChildren(); foreach (var childObject in childNetworkObjects) { @@ -1245,6 +1245,14 @@ internal void OnClientDisconnectFromServer(ulong clientId) { continue; } + + // If the child's owner is not the client disconnected and the objects are marked with either distributable or transferable, then + // do not change ownership. + if (childObject.OwnerClientId != clientId && (childObject.IsOwnershipDistributable || childObject.IsOwnershipTransferable)) + { + continue; + } + NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true); if (EnableDistributeLogging) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index c449c9aadb..cf8f1b2bb5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1659,7 +1659,20 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } if (NetworkManager.NetworkConfig.EnableSceneManagement) { - NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; + if (!NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(gameObject.scene.handle)) + { + // Most likely this issue is due to an integration test + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"Failed to find scene handle {gameObject.scene.handle} for {gameObject.name}!"); + } + // Just use the existing handle + NetworkSceneHandle = gameObject.scene.handle; + } + else + { + NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; + } } if (DontDestroyWithOwner && !IsOwnershipDistributable) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index beba9c7055..21ac399dd5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1755,16 +1755,24 @@ internal void GetObjectDistribution(ref Dictionary(); + if (parentNetworkObject != null && parentNetworkObject.OwnerClientId == networkObject.OwnerClientId + && (networkObject.IsOwnershipDistributable || networkObject.IsOwnershipTransferable)) { - var parentNetworkObject = networkObject.transform.parent.GetComponent(); - if (parentNetworkObject != null && parentNetworkObject.OwnerClientId == networkObject.OwnerClientId) - { - continue; - } + continue; } + } + + // At this point we only allow things marked with the distributable permission and is not locked to be distributed + if (networkObject.IsOwnershipDistributable && !networkObject.IsOwnershipLocked) + { + // We have to check if it is an in-scene placed NetworkObject and if it is get the source prefab asset GlobalObjectIdHash value of the in-scene placed instance // since all in-scene placed instances use unique GlobalObjectIdHash values. var globalOjectIdHash = networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value ? networkObject.InScenePlacedSourceGlobalObjectIdHash : networkObject.GlobalObjectIdHash; @@ -1879,8 +1887,29 @@ internal void DistributeNetworkObjects(ulong clientId) { if ((i % offsetCount) == 0) { + var children = ownerList.Value[i].GetComponentsInChildren(); + // Since the ownerList.Value[i] has to be distributable, then transfer all child NetworkObjects + // with the same owner clientId and are marked as distributable also to the same client to keep + // the owned distributable parent with the owned distributable children + foreach (var child in children) + { + // Ignore the parent and any child that does not have the same owner or that is already owned by the currently targeted client + if (child == ownerList.Value[i] || child.OwnerClientId != ownerList.Value[i].OwnerClientId || child.OwnerClientId == clientId) + { + continue; + } + if ((!child.IsOwnershipDistributable || !child.IsOwnershipTransferable) && NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"Sibling {child.name} of root parent {ownerList.Value[i].name} is neither transferrable or distributable! Object distribution skipped and could lead to a potentially un-owned or owner-mismatched {nameof(NetworkObject)}!"); + continue; + } + // Transfer ownership of all distributable =or= transferrable children with the same owner to the same client to preserve the sibling ownership tree. + ChangeOwnership(child, clientId, true); + // Note: We don't increment the distributed count for these children as they are skipped when getting the object distribution + } + // Finally, transfer ownership of the root parent ChangeOwnership(ownerList.Value[i], clientId, true); - //if (EnableDistributeLogging) + if (EnableDistributeLogging) { Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}"); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs new file mode 100644 index 0000000000..bd74d710a2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs @@ -0,0 +1,291 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + internal class ParentChildDistibutionTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 4; + + private GameObject m_GenericPrefab; + private ulong m_OriginalOwnerId; + private List m_TargetSpawnedObjects = new List(); + private List m_AltTargetSpawnedObjects = new List(); + private Dictionary> m_AncillarySpawnedObjects = new Dictionary>(); + private List m_NetworkManagers = new List(); + private StringBuilder m_ErrorMsg = new StringBuilder(); + + public ParentChildDistibutionTests() : base(HostOrServer.DAHost) + { + } + + protected override IEnumerator OnTearDown() + { + m_NetworkManagers.Clear(); + m_TargetSpawnedObjects.Clear(); + m_AncillarySpawnedObjects.Clear(); + m_AltTargetSpawnedObjects.Clear(); + return base.OnTearDown(); + } + + protected override void OnServerAndClientsCreated() + { + m_GenericPrefab = CreateNetworkObjectPrefab("GenPrefab"); + var networkObject = m_GenericPrefab.GetComponent(); + networkObject.DontDestroyWithOwner = true; + base.OnServerAndClientsCreated(); + } + + private bool AllTargetedInstancesSpawned() + { + m_ErrorMsg.Clear(); + foreach (var client in m_NetworkManagers) + { + foreach (var spawnedObject in m_TargetSpawnedObjects) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(spawnedObject.NetworkObjectId)) + { + m_ErrorMsg.AppendLine($"{client.name} has not spawned {spawnedObject.name}!"); + } + } + } + + return m_ErrorMsg.Length == 0; + } + + private bool AllAncillaryInstancesSpawned() + { + m_ErrorMsg.Clear(); + foreach (var client in m_NetworkManagers) + { + foreach (var clientObjects in m_AncillarySpawnedObjects) + { + foreach (var spawnedObject in clientObjects.Value) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(spawnedObject.NetworkObjectId)) + { + m_ErrorMsg.AppendLine($"{client.name} has not spawned {spawnedObject.name}!"); + } + } + } + } + + return m_ErrorMsg.Length == 0; + } + + /// + /// Validates that a new owner is assigned to all of the targeted objects + /// + private bool TargetedObjectsChangedOwnership() + { + m_ErrorMsg.Clear(); + var newOwnerId = m_OriginalOwnerId; + foreach (var spawnedObject in m_AltTargetSpawnedObjects) + { + var isParentLocked = spawnedObject.transform.parent != null ? spawnedObject.transform.parent.GetComponent().IsOwnershipLocked : false; + if (!isParentLocked && !spawnedObject.IsOwnershipLocked && spawnedObject.OwnerClientId == m_OriginalOwnerId) + { + m_ErrorMsg.AppendLine($"{spawnedObject.name} still is owned by Client-{m_OriginalOwnerId}!"); + } + else if (!isParentLocked && !spawnedObject.IsOwnershipLocked && m_OriginalOwnerId == newOwnerId) + { + newOwnerId = spawnedObject.OwnerClientId; + } + else if ((isParentLocked || spawnedObject.IsOwnershipLocked) && spawnedObject.OwnerClientId != m_OriginalOwnerId) + { + if (isParentLocked) + { + m_ErrorMsg.AppendLine($"{spawnedObject.name}'s parent was locked but its owner changed to Client-{m_OriginalOwnerId}!"); + } + else + { + m_ErrorMsg.AppendLine($"{spawnedObject.name} was locked but its owner changed to Client-{m_OriginalOwnerId}!"); + } + } + + if (spawnedObject.OwnerClientId != newOwnerId) + { + m_ErrorMsg.AppendLine($"{spawnedObject.name} is not owned by Client-{newOwnerId}!"); + } + } + return m_ErrorMsg.Length == 0; + } + + private bool AncillaryObjectsKeptOwnership() + { + m_ErrorMsg.Clear(); + foreach (var clientObjects in m_AncillarySpawnedObjects) + { + foreach (var spawnedObject in clientObjects.Value) + { + if (spawnedObject.OwnerClientId != clientObjects.Key) + { + m_ErrorMsg.AppendLine($"{spawnedObject.name} changed ownership to Client-{spawnedObject.OwnerClientId}!"); + } + } + } + return m_ErrorMsg.Length == 0; + } + + public enum DistributionTypes + { + UponConnect, + UponDisconnect + } + + public enum OwnershipLocking + { + NoLocking, + LockRootParent, + LockTargetChild, + } + + + [UnityTest] + public IEnumerator DistributeOwnerHierarchy([Values] DistributionTypes distributionType, [Values] OwnershipLocking ownershipLock) + { + m_NetworkManagers.Clear(); + m_TargetSpawnedObjects.Clear(); + m_AncillarySpawnedObjects.Clear(); + m_AltTargetSpawnedObjects.Clear(); + + if (distributionType == DistributionTypes.UponConnect) + { + m_ClientNetworkManagers[3].Shutdown(); + } + + if (!UseCMBService()) + { + m_NetworkManagers.Add(m_ServerNetworkManager); + } + m_NetworkManagers.AddRange(m_ClientNetworkManagers); + if (distributionType == DistributionTypes.UponConnect) + { + m_NetworkManagers.Remove(m_ClientNetworkManagers[3]); + } + + // When testing connect redistribution, + var instances = distributionType == DistributionTypes.UponDisconnect ? 1 : 2; + var rootObject = (GameObject)null; + var childOne = (GameObject)null; + var childTwo = (GameObject)null; + var networkObject = (NetworkObject)null; + + for (int i = 0; i < instances; i++) + { + rootObject = SpawnObject(m_GenericPrefab, m_ClientNetworkManagers[0]); + networkObject = rootObject.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); + if (ownershipLock == OwnershipLocking.LockRootParent && distributionType == DistributionTypes.UponConnect) + { + networkObject.SetOwnershipLock(true); + } + m_TargetSpawnedObjects.Add(networkObject); + + // Used to validate nested transferable transfers to the same owner + childOne = SpawnObject(m_GenericPrefab, m_ClientNetworkManagers[0]); + networkObject = childOne.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + m_TargetSpawnedObjects.Add(networkObject); + + // Used to validate nested distributable transfers to the same owner + childTwo = SpawnObject(m_GenericPrefab, m_ClientNetworkManagers[0]); + networkObject = childTwo.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); + if (ownershipLock == OwnershipLocking.LockTargetChild && distributionType == DistributionTypes.UponConnect) + { + networkObject.SetOwnershipLock(true); + } + m_TargetSpawnedObjects.Add(childTwo.GetComponent()); + + childOne.transform.parent = rootObject.transform; + childTwo.transform.parent = rootObject.transform; + } + yield return WaitForConditionOrTimeOut(AllTargetedInstancesSpawned); + AssertOnTimeout($"Timed out waiting for all targeted cloned instances to spawn!\n {m_ErrorMsg}"); + + // Used to validate that other children do not transfer ownership when redistributing. + + var altAchildOne = SpawnObject(m_GenericPrefab, m_ClientNetworkManagers[1]); + var altAchildTwo = SpawnObject(m_GenericPrefab, m_ClientNetworkManagers[1]); + m_AncillarySpawnedObjects.Add(m_ClientNetworkManagers[1].LocalClientId, new List()); + networkObject = altAchildOne.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); + m_AncillarySpawnedObjects[m_ClientNetworkManagers[1].LocalClientId].Add(networkObject); + altAchildOne.transform.parent = rootObject.transform; + + networkObject = altAchildTwo.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + m_AncillarySpawnedObjects[m_ClientNetworkManagers[1].LocalClientId].Add(networkObject); + altAchildTwo.transform.parent = rootObject.transform; + + var altBchildOne = SpawnObject(m_GenericPrefab, m_ClientNetworkManagers[2]); + var altBchildTwo = SpawnObject(m_GenericPrefab, m_ClientNetworkManagers[2]); + m_AncillarySpawnedObjects.Add(m_ClientNetworkManagers[2].LocalClientId, new List()); + networkObject = altBchildOne.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Distributable); + m_AncillarySpawnedObjects[m_ClientNetworkManagers[2].LocalClientId].Add(networkObject); + altBchildOne.transform.parent = rootObject.transform; + + networkObject = altBchildTwo.GetComponent(); + networkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + m_AncillarySpawnedObjects[m_ClientNetworkManagers[2].LocalClientId].Add(networkObject); + altBchildTwo.transform.parent = rootObject.transform; + + yield return WaitForConditionOrTimeOut(AllAncillaryInstancesSpawned); + AssertOnTimeout($"Timed out waiting for all ancillary cloned instances to spawn!\n {m_ErrorMsg}"); + + // Now disconnect the client that owns the rootObject + // Get the original clientId + m_OriginalOwnerId = m_ClientNetworkManagers[0].LocalClientId; + + if (distributionType == DistributionTypes.UponDisconnect) + { + // Swap out the original owner's NetworkObject with one of the other client's since those instances will + // be destroyed when the client disconnects. + foreach (var entry in m_TargetSpawnedObjects) + { + m_AltTargetSpawnedObjects.Add(m_ClientNetworkManagers[1].SpawnManager.SpawnedObjects[entry.NetworkObjectId]); + } + // Disconnect the client to trigger object redistribution + m_ClientNetworkManagers[0].Shutdown(); + } + else + { + m_ClientNetworkManagers[3].StartClient(); + yield return WaitForConditionOrTimeOut(() => m_ClientNetworkManagers[3].IsConnectedClient); + AssertOnTimeout($"{m_ClientNetworkManagers[3].name} failed to reconnect!"); + } + + // Verify all of the targeted objects changed ownership to the same client + yield return WaitForConditionOrTimeOut(TargetedObjectsChangedOwnership); + AssertOnTimeout($"All targeted objects did not get distributed to the same owner!\n {m_ErrorMsg}"); + + // When enabled, you should see one of the two root instances that have children get distributed to + // the reconnected client. + if (m_EnableVerboseDebug && distributionType == DistributionTypes.UponConnect) + { + m_ErrorMsg.Clear(); + m_ErrorMsg.AppendLine($"Original targeted objects owner: {m_OriginalOwnerId}"); + foreach (var spawnedObject in m_TargetSpawnedObjects) + { + m_ErrorMsg.AppendLine($"{spawnedObject.name} new owner: {spawnedObject.OwnerClientId}"); + } + Debug.Log($"{m_ErrorMsg}"); + } + + // We only want to make sure no other children owned by still connected clients change ownership + if (distributionType == DistributionTypes.UponDisconnect) + { + // Verify the ancillary objects kept the same ownership + yield return WaitForConditionOrTimeOut(AncillaryObjectsKeptOwnership); + AssertOnTimeout($"All ancillary objects did not get distributed to the same owner!\n {m_ErrorMsg}"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs.meta new file mode 100644 index 0000000000..1cf2f2310b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ParentChildDistibutionTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 45d9d6508546cda4dba613ef82b6e51e \ No newline at end of file From be45e865bcb079e0afc9c69de42f730a8225cc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:36:02 +0100 Subject: [PATCH 170/236] build: NGO CI update and rewrite [2.X] (#3193) * Refactor of CI setup, jobs and triggers * Disabling some tests which failing on mobile devices * Setting up Renovate bot configuration (same as already present in develop branch) * Added pvpExceptions file --------- Co-authored-by: NoelStephensUnity --- .github/renovate.json5 | 39 + .github/workflows/renovate-validation.yml | 38 - .github/workflows/renovate.yml | 33 - .yamato/_run-all.yml | 220 ++-- .yamato/_triggers.yml | 163 ++- .yamato/code-coverage.yml | 30 +- .yamato/console-standalone-test.yml | 96 ++ .yamato/desktop-standalone-tests.yml | 107 ++ .yamato/disable-burst-if-requested.py | 4 + .yamato/mobile-build-and-test.yml | 153 --- .yamato/mobile-standalone-test.yml | 106 ++ .yamato/multiprocess-project-tests.yml | 42 - .yamato/package-pack.yml | 24 + .yamato/package-tests.yml | 55 +- .yamato/performance-tests.yml | 55 + .yamato/project-pack.yml | 43 +- .yamato/project-promotion.yml | 74 -- .yamato/project-publish.yml | 67 - .yamato/project-standards.yml | 31 +- .yamato/project-tests.yml | 32 +- .yamato/project-updated-dependencies-test.yml | 54 +- .yamato/project.metafile | 227 ++-- .yamato/standalone-project-tests.yml | 40 - .yamato/webgl-build.yml | 64 +- .../NetworkTransformAnticipationTests.cs | 3 + .../NetworkVariable/NetworkVariableTests.cs | 4 + .../pvpExceptions.json | 1073 +++++++++++++++++ .../pvpExceptions.json.meta | 7 + 28 files changed, 1990 insertions(+), 894 deletions(-) create mode 100644 .github/renovate.json5 delete mode 100644 .github/workflows/renovate-validation.yml delete mode 100644 .github/workflows/renovate.yml create mode 100644 .yamato/console-standalone-test.yml create mode 100644 .yamato/desktop-standalone-tests.yml delete mode 100644 .yamato/mobile-build-and-test.yml create mode 100644 .yamato/mobile-standalone-test.yml delete mode 100644 .yamato/multiprocess-project-tests.yml create mode 100644 .yamato/package-pack.yml create mode 100644 .yamato/performance-tests.yml delete mode 100644 .yamato/project-promotion.yml delete mode 100644 .yamato/project-publish.yml delete mode 100644 .yamato/standalone-project-tests.yml create mode 100644 com.unity.netcode.gameobjects/pvpExceptions.json create mode 100644 com.unity.netcode.gameobjects/pvpExceptions.json.meta diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000000..acab47d6f0 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,39 @@ +{ + "baseBranches": ["develop", "develop-2.0.0"], + "dependencyDashboard": true, + + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>Unity-Technologies/unity-renovate-config" + ], + "prConcurrentLimit": 100, + // Ignore commits produced by github actions workflows + "gitIgnoredAuthors": ["githubaction@githubaction.com"], + "ignorePaths": [ + "**/node_modules/**", + // Don't renovate files in special folders using ~ as suffix + "**/*~/**" + ], + "packageRules": [ + + // Run unity-upm-project and unity-upm-package only on weekends to reduce PR noise + // Also ensure dependencies won't be downgraded when they don't exist in the public repositories + { + "matchManagers": [ + "unity-upm-project", + "unity-upm-package" + ], + "enabled": "true", + "schedule": [ + "every weekend" + ], + "rollbackPrs": false + }, + + // Enable automerge for Bokken image updates + { + "matchDatasources": ["unity-bokken"], + "automerge": false, + }, + ], +} diff --git a/.github/workflows/renovate-validation.yml b/.github/workflows/renovate-validation.yml deleted file mode 100644 index 9ef821bcbd..0000000000 --- a/.github/workflows/renovate-validation.yml +++ /dev/null @@ -1,38 +0,0 @@ -# This workflow is for validating the Renovate configuration and docker image -# updates for it. -name: Renovate Validation -on: - workflow_dispatch: - inputs: - log-level: - type: choice - description: Select log level for Renovate - options: - - trace - - debug - - info - - warn - - error - default: info - required: false - pull_request: - paths: - # we trigger validation on any changes to the renovate workflow files - - .github/workflows/renovate*.yml - # as well as for any possible location for the renovate config file - - .github/renovate.json? - - -jobs: - renovate-validation: - # The reusable workflow will be updated by renovate if there's a new version - uses: Unity-Technologies/renovate-workflows/.github/workflows/run.yml@v5.0.0 - with: - # This is the image that contains our custom renovate and will be auto - # updated by Renovate itself. - image: europe-docker.pkg.dev/unity-cds-services-prd/ds-docker/renovate:10.1.3@sha256:fdeed7bb524bd67611eb91ee1a5e990c8c73ed62c84a0cd5ef66c87eb5fd0d70 - dry-run: full - log-level: ${{ github.event.inputs.log-level }} - secrets: - renovate-auth-secret: ${{ secrets.RENOVATE_AUTH_SECRET }} - github-com-token: ${{ secrets.GH_COM_TOKEN }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml deleted file mode 100644 index f859c46a94..0000000000 --- a/.github/workflows/renovate.yml +++ /dev/null @@ -1,33 +0,0 @@ -# This workflow runs Renovate against the current repo and will create PRs with outdated dependencies. -name: Renovate - -on: - workflow_dispatch: - inputs: - log-level: - type: choice - description: Select log level for Renovate - options: - - trace - - debug - - info - - warn - - error - default: info - required: false - schedule: - # Every 6 hours at the 6th minute. - - cron: '06 */6 * * *' - -jobs: - renovate: - # The reusable workflow will be updated by renovate if there's a new version - uses: Unity-Technologies/renovate-workflows/.github/workflows/run.yml@v5.0.0 - with: - # This is the image that contains our custom renovate and will be auto - # updated by Renovate itself. - image: europe-docker.pkg.dev/unity-cds-services-prd/ds-docker/renovate:10.1.3@sha256:fdeed7bb524bd67611eb91ee1a5e990c8c73ed62c84a0cd5ef66c87eb5fd0d70 - log-level: ${{ github.event.inputs.log-level }} - secrets: - renovate-auth-secret: ${{ secrets.RENOVATE_AUTH_SECRET }} - github-com-token: ${{ secrets.GH_COM_TOKEN }} \ No newline at end of file diff --git a/.yamato/_run-all.yml b/.yamato/_run-all.yml index 8bf0c61968..af4c78b7f0 100644 --- a/.yamato/_run-all.yml +++ b/.yamato/_run-all.yml @@ -1,161 +1,173 @@ {% metadata_file .yamato/project.metafile %} --- -run_all_tests: - name: Run All Package and Project Tests + +# Runs all package tests +run_all_package_tests: + name: Run All Package Tests dependencies: - # Pull in package and validate jobs through the badges job - - .yamato/_triggers.yml#badges_test_trigger - - .yamato/mobile-build-and-test.yml#run_{{ projects.first.name }}_tests_{{ mobile_validation_editor }}_iOS - - .yamato/mobile-build-and-test.yml#run_{{ projects.first.name }}_tests_{{ mobile_validation_editor }}_android - # - .yamato/_run-all.yml#all_project_tests_standalone -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} - - .yamato/webgl-build.yml#build_{{ project.name }}_tests_{{ editor }}_webgl +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} + - .yamato/package-tests.yml#package_test_-_ngo_{{ editor }}_{{ platform.name }} {% endfor -%} -{% endif -%} {% endfor -%} -{% for platform in test_platforms -%} -{% for project in projects -%} -{% for editor in project.test_editors -%} -{% if editor != "trunk" -%} -{% for package in project.packages -%} - - .yamato/package-tests.yml#test_{{ project.name}}_{{ package.name }}_{{ editor }}_{{ platform.name }} + +# Runs all package tests on trunk editor +run_all_package_tests_trunk: + name: Run All Package Tests [Trunk only] + dependencies: +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.default -%} + - .yamato/package-tests.yml#package_test_-_ngo_{{ editor }}_{{ platform.name }} {% endfor -%} - - .yamato/project-tests.yml#test_{{ project.name }}_{{ editor }}_{{ platform.name }} -{% endif -%} {% endfor -%} -## Test minimal project with different versions of dependencies -{% if project.name == "minimalproject" -%} -{% for dependency in dependencies -%} -{% for depeditor in dependency.test_editors -%} -{% if depeditor != "trunk" -%} - - .yamato/package-tests.yml#test_compatibility_{{ project.name }}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ depeditor }}_{{ platform.name }} -{% endif -%} + +# Runs all projects tests +run_all_project_tests: + name: Run All Project Tests + dependencies: +{% for project in projects.all -%} +{% if project.has_tests == "true" -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} + - .yamato/project-tests.yml#test_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endif -%} {% endfor -%} -{% endfor -%} -run_all_tests_trunk: - name: Run All Package and Project Tests [Trunk] + +# Runs all projects tests on trunk editor +run_all_project_tests_trunk: + name: Run All Project Tests [Trunk only] dependencies: -{% for platform in test_platforms -%} -{% for project in projects -%} -{% for editor in project.test_editors -%} -{% if editor == "trunk" -%} -{% for package in project.packages -%} - - .yamato/package-tests.yml#test_{{ project.name}}_{{ package.name }}_{{ editor }}_{{ platform.name }} -{% endfor -%} - - .yamato/project-tests.yml#test_{{ project.name }}_{{ editor }}_{{ platform.name }} +{% for project in projects.all -%} +{% if project.has_tests == "true" -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.default -%} + - .yamato/project-tests.yml#test_{{ project.name }}_{{ platform.name }}_{{ editor }} +{% endfor -%} +{% endfor -%} {% endif -%} {% endfor -%} -## Test minimal project with different versions of dependencies on trunk -{% if project.name == "minimalproject" -%} -{% for dependency in dependencies -%} -{% for depeditor in dependency.test_editors -%} -{% if depeditor == "trunk" -%} - - .yamato/package-tests.yml#test_compatibility_{{ project.name }}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ depeditor }}_{{ platform.name }} -{% endif -%} + +# Runs all project standards check +run_all_projects_standards: + name: Run All Projects Standards + dependencies: +{% for platform in test_platforms.default -%} +{% for project in projects.all -%} +{% for editor in validation_editors.default -%} + - .yamato/project-standards.yml#standards_{{ platform.name }}_{{ project.name }}_{{ editor }} {% endfor -%} {% endfor -%} -{% endif -%} +{% endfor -%} + + +# Runs all WebGL builds +run_all_webgl_builds: + name: Run All WebGl Build + dependencies: +{% for project in projects.default -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} + - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform }}_{{ editor }} +{% endfor -%} {% endfor -%} {% endfor -%} -all_project_tests: - name: Run All Project Tests + +# Runs all WebGL builds on trunk editor +run_all_webgl_builds_trunk: + name: Run All WebGl Build [Trunk only] dependencies: - - .yamato/_triggers.yml#badges_test_trigger -{% for platform in test_platforms -%} -{% for project in projects -%} -{% for editor in project.test_editors -%} - - .yamato/project-tests.yml#test_{{ projects.first.name }}_{{ editor }}_{{ platform.name }} +{% for project in projects.default -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.default -%} + - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} -all_package_tests: - name: Run All Package Tests + +# Runs all Desktop tests +run_all_project_tests_desktop_standalone: + name: Run All Standalone Tests - Desktop dependencies: - - .yamato/_triggers.yml#badges_test_trigger -{% for platform in test_platforms -%} -{% for project in projects -%} -{% for editor in project.test_editors -%} -{% for package in project.packages -%} - - .yamato/package-tests.yml#test_{{ project.name}}_{{ package.name }}_{{ editor }}_{{ platform.name }} +{% for project in projects.default -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +{% for backend in scripting_backends -%} + - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} {% endfor -%} -# Test minimal project with different versions of dependencies -all_compatibility_tests: - name: Run All Compatibility Tests + +# Runs all Desktop tests on trunk editor +run_all_project_tests_desktop_standalone_trunk: + name: Run All Standalone Tests - Desktop [Trunk only] dependencies: -{% for platform in test_platforms -%} -{% for project in projects -%} -{% if project.name == "minimalproject" -%} -{% for dependency in dependencies -%} -{% for editor in dependency.test_editors -%} - - .yamato/package-tests.yml#test_compatibility_{{ project.name }}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ editor }}_{{ platform.name }} +{% for project in projects.default -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.default -%} +{% for backend in scripting_backends -%} + - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ editor }} {% endfor -%} {% endfor -%} -{% endif -%} {% endfor -%} {% endfor -%} -all_singlenode_multiprocess_tests: - name: Run All Multiprocess Tests - Single Node +# Runs all Mobile tests +run_all_project_tests_mobile_standalone: + name: Run All Standalone Tests - Mobile dependencies: -{% for platform in test_platforms -%} -{% for project in projects -%} -{% for editor in project.test_editors -%} -{% if editor != "trunk" %} - - .yamato/multiprocess-project-tests.yml#singlenode_multiprocess_test_testproject_{{ editor }}_{{ platform.name }} -{% endif %} +{% for project in projects.default -%} +{% for platform in test_platforms.mobile_test -%} +{% for editor in validation_editors.all -%} + - .yamato/mobile-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} -all_project_tests_standalone: - name: Run All Project Tests - Standalone + +# Runs all Mobile tests on trunk editor +run_all_project_tests_mobile_standalone_trunk: + name: Run All Standalone Tests - Mobile [Trunk only] dependencies: -{% for platform in test_platforms -%} -{% for project in projects -%} -{% if project.has_tests == "true" -%} -{% for editor in project.test_editors -%} -{% for backend in scripting_backends -%} - - .yamato/standalone-project-tests.yml#standalone_tests_{{ project.name }}_{{ backend }}_{{ editor }}_{{ platform.name }} +{% for project in projects.default -%} +{% for platform in test_platforms.mobile_test -%} +{% for editor in validation_editors.default -%} + - .yamato/mobile-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} -{% endif -%} -{% endfor -%} {% endfor -%} -all_project_tests_mobile: - name: Run All Project Tests - Mobile + +# Runs all Console tests +run_all_project_tests_console_standalone: + name: Run All Standalone Tests - Console dependencies: -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} - - .yamato/mobile-build-and-test.yml#run_{{ project.name }}_tests_{{ editor }}_android - - .yamato/mobile-build-and-test.yml#run_{{ project.name }}_tests_{{ editor }}_iOS +{% for project in projects.default -%} +{% for platform in test_platforms.console_test -%} +{% for editor in validation_editors.all -%} + - .yamato/console-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} +{% endfor -%} {% endfor -%} -{% endif -%} {% endfor -%} -all_project_tests_webgl: - name: Build All Project Tests - WebGL + +# Runs all Console tests on trunk editor +run_all_project_tests_console_standalone_trunk: + name: Run All Standalone Tests - Console [Trunk only] dependencies: -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} - - .yamato/webgl-build.yml#build_{{ project.name }}_tests_{{ editor }}_webgl +{% for project in projects.default -%} +{% for platform in test_platforms.console_test -%} +{% for editor in validation_editors.default -%} + - .yamato/console-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} +{% endfor -%} {% endfor -%} -{% endif -%} {% endfor -%} diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index e225500f59..8abd3622cf 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -1,100 +1,85 @@ {% metadata_file .yamato/project.metafile %} --- -develop_nightly: - name: "\U0001F319 [Nightly] Run All Tests" - triggers: - recurring: - - branch: develop - frequency: daily - rerun: always - dependencies: - - .yamato/_run-all.yml#run_all_tests -{% for project in projects -%} -{% if project.has_tests == "true" -%} - - .yamato/code-coverage.yml#code_coverage_win_{{ project.name }}_{{ validation_editor }} -{% endif -%} -{% endfor -%} -develop_weekly_trunk: - name: "\U0001F319 [Weekly] Run All Tests [Trunk]" - triggers: - recurring: - - branch: develop - frequency: weekly - rerun: always +# Run all relevant tasks when a pull request targeting the develop or release branch is created or updated. +pull_request_trigger: + name: Pull Request Trigger (develop, develop-2.0.0, & release branches) dependencies: - - .yamato/_run-all.yml#run_all_tests_trunk + # Run project standards to verify package/default project + - .yamato/project-standards.yml#standards_ubuntu_testproject_trunk + # Run package EditMode and Playmode tests on desktop platforms on trunk + - .yamato/_run-all.yml#run_all_package_tests_trunk + # Run project EditMode and PLaymode tests on desktop platforms on trunk + - .yamato/_run-all.yml#run_all_project_tests_trunk + # Run one standalone test to make sure there are no obvious issues with most common platform (for example --fail-on-assert option is not present with package/project tests) + - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_win_il2cpp_trunk + triggers: + cancel_old_ci: true + pull_requests: + - targets: + only: + - "develop" + - "develop-2.0.0" + - "/release\/.*/" + - drafts: false + -multiprocess_nightly: - name: "\U0001F319 [Nightly] Run Multiprocess Tests" +# Run all tests on trunk on nightly basis. +# Same subset as pull_request_trigger with addition of mobile/desktop/console tests and webgl builds +# Those tests are all running on trunk editor (since it's daily and running all of them would add a lot of overhead) +develop_nightly: + name: "\U0001F319 [Nightly] Run All Tests [Trunk]" triggers: recurring: - - branch: develop - frequency: daily - rerun: always + - branch: develop-2.0.0 + frequency: daily + rerun: always dependencies: - - .yamato/_run-all.yml#all_singlenode_multiprocess_tests + # Run project standards to verify package/default project + - .yamato/project-standards.yml#standards_ubuntu_testproject_trunk + # Run APV jobs to make sure the change won't break any dependants + - .yamato/wrench/preview-a-p-v.yml#all_preview_apv_jobs + # Run package EditMode and Playmode tests on desktop platforms on trunk + - .yamato/_run-all.yml#run_all_package_tests_trunk + # Run project EditMode and PLaymode tests on desktop platforms on trunk + - .yamato/_run-all.yml#run_all_project_tests_trunk + # Run Runtime tests on desktop players on trunk + - .yamato/_run-all.yml#run_all_project_tests_desktop_standalone_trunk + # Run Runtime tests on mobile players on trunk + - .yamato/_run-all.yml#run_all_project_tests_mobile_standalone_trunk + # Run Runtime tests on console players on trunk + - .yamato/_run-all.yml#run_all_project_tests_console_standalone_trunk + # Build player for webgl platform on trunk + - .yamato/_run-all.yml#run_all_webgl_builds_trunk + # Build player for webgl platform on trunk + - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_ubuntu_trunk -# Run all relevant tasks when a pull request targeting the develop -# branch is created or updated. Currently only netcode package tests are -# enabled, since the others are missing test coverage and will fail CI. -pull_request_trigger: - name: Pull Request Trigger (master, develop, & release branches) - dependencies: - - .yamato/project-standards.yml#standards_{{ projects.first.name }} -{% for project in projects -%} -{% for package in project.packages -%} -{% if project.validate == "true" -%} - - .yamato/project-publish.yml#validate_{{ package.name }}_{{ test_platforms.first.name }}_{{ validation_editor }} -{% endif -%} -{% endfor -%} -{% for platform in test_platforms -%} -{% for package in project.packages -%} - - .yamato/package-tests.yml#test_{{ project.name }}_{{ package.name }}_{{ project.test_editors.first }}_{{ platform.name }} -{% endfor -%} - - .yamato/project-tests.yml#test_{{ project.name }}_{{ project.test_editors.first }}_{{ platform.name }} -{% endfor -%} -{% endfor -%} - triggers: - cancel_old_ci: true - pull_requests: - - targets: - only: - - "master" - - "develop" - - "develop-2.0.0" - - "/release\/.*/" -# Currently, we need to have a trigger to updated badges -# Only package badges currently exist -badges_test_trigger: - name: ⚡ Badges Tests Trigger - agent: - type: Unity::VM - image: package-ci/ubuntu-22.04:v4 - flavor: b1.small - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci package izon -t - artifacts: - logs: - paths: - - "upm-ci~/test-results/**/*" - packages: - paths: - - "upm-ci~/packages/**/*" +# Run all tests on weekly bases +# Same subset as develop_nightly but runs per all supported editors as well as executes code coverage test and runs project standards per project +# It's not running wrench jobs since those will run either way in nightly test run +# This in contrast to nightly checks will run tests on all editors (not only trunk). Running those on weekly basis and trunk tests nightly should be a good balance between making sure that tests are passing and overhead of running lots of tests +develop_weekly_trunk: + name: "\U0001F319 [Weekly] Run All Tests" + triggers: + recurring: + - branch: develop-2.0.0 + frequency: weekly + rerun: always dependencies: -{% for project in projects -%} -{% for package in project.packages -%} -{% if project.validate == "true" -%} - - .yamato/project-publish.yml#validate_{{ package.name }}_{{ test_platforms.first.name }}_{{ validation_editor }} -{% endif -%} -{% for editor in project.test_editors -%} -{% if editor != "trunk" -%} -{% for platform in test_platforms -%} - - .yamato/package-tests.yml#test_{{ project.name }}_{{ package.name }}_{{ editor }}_{{ platform.name }} -{% endfor -%} -{% endif -%} -{% endfor -%} -{% endfor -%} -{% endfor -%} + # Run project standards to verify package/default project + - .yamato/_run-all.yml#run_all_projects_standards + # Run package EditMode and Playmode tests on desktop platforms + - .yamato/_run-all.yml#run_all_package_tests + # Run project EditMode and PLaymode tests on desktop platforms + - .yamato/_run-all.yml#run_all_project_tests + # Run Runtime tests on desktop players + - .yamato/_run-all.yml#run_all_project_tests_desktop_standalone + # Run Runtime tests on mobile players + - .yamato/_run-all.yml#run_all_project_tests_mobile_standalone + # Run Runtime tests on console players + - .yamato/_run-all.yml#run_all_project_tests_console_standalone + # Build player for webgl platform on trunk + - .yamato/_run-all.yml#run_all_webgl_builds + # Run code coverage test + - .yamato/code-coverage.yml#code_coverage_ubuntu_trunk \ No newline at end of file diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index 42ed015fee..0e2d499f8d 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -1,23 +1,27 @@ {% metadata_file .yamato/project.metafile %} --- -{% for project in projects -%} -{% if project.has_tests == "true" -%} -code_coverage_win_{{ project.name }}_{{ validation_editor }}: - name: Code Coverage Report - Windows - {{ project.name }} - {{ validation_editor }} + +# Runs package tests in order to determine code coverage of the NGO package. +# In essence it's performing the same task as .yamato/package-test jobs with the overhead being the measured code coverage +# It's ok for code coverage to be performed only on one platform (default) since code coverage won't change much between those. +# Default platform was chosen (ubuntu) since it's the fastest and most resource friendly with default editor. +{% for platform in test_platforms.default -%} +{% for editor in validation_editors.default -%} +code_coverage_{{ platform.name }}_{{ editor }}: + name: Code Coverage - NGO [{{ platform.name }}, {{ editor }}] agent: - type: Unity::VM - image: package-ci/win10:v4 - flavor: b1.large + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} commands: - - pip install unity-downloader-cli --upgrade --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - unity-downloader-cli -u {{ validation_editor }} -c editor --wait --fast - - upm-ci package test -u .Editor --package-path com.unity.netcode.gameobjects --enable-code-coverage --code-coverage-options 'enableCyclomaticComplexity;generateAdditionalMetrics;generateHtmlReport;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime,+Unity.Netcode.Components' + - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait + - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %} upm-ci package test -u {{ editor }} --package-path com.unity.netcode.gameobjects --enable-code-coverage --code-coverage-options 'generateAdditionalMetrics;generateHtmlReport;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime' --extra-utr-arg="--extra-editor-arg=--burst-disable-compilation --extra-editor-arg=-testCategory --extra-editor-arg=!Performance --timeout=1800 --reruncount=1 --clean-library-on-rerun" artifacts: logs: paths: - "upm-ci~/test-results/**/*" dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} -{% endif -%} + - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} +{% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/console-standalone-test.yml b/.yamato/console-standalone-test.yml new file mode 100644 index 0000000000..2d5ca4861e --- /dev/null +++ b/.yamato/console-standalone-test.yml @@ -0,0 +1,96 @@ +{% metadata_file .yamato/project.metafile %} +--- + +# Builds a player on console standalone platform and executes RuntimeTests (equivalent to PlayMode tests) of the NGO package in the Standalone build. +# Default project (testptoject) in this case is used as a context. +# Builds/Tests are made on each console platform (PS4, PS5, Switch, XboxOne, XboxSeriesX) as in project.metafile declaration +# Builds/Tests are made on each supported editor as in project.metafile declaration + +# For SOME of the console devices it's necessary to split build and run phases so it was split for all +# For all consoles we need to use il2cpp scripting backend (so no testing with mono) +# Switch works only with ARM64 and the rest with x64 architectures +# For now all platforms used for building are windows based + +# Builds are made with x64 architecture machines since those are compatible to run on ARM64 devices +{% for project in projects.default -%} +{% for platform in test_platforms.console_build -%} +{% for editor in validation_editors.all -%} +build_{{ project.name }}_{{ platform.name }}_{{ editor }}: + name: Build {{ project.name }} - [{{ platform.name }}, {{ editor }}, il2cpp{% if platform.name == "switch" %}, arm64{% endif %}] + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + commands: + # Installing tools. unity-downloader-cli and utr should be already preinstalled on the image + - sudo pip install unity-downloader-cli + - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp -c {{ platform.name }} --fast --wait + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat + + # Platform specific Build + - utr --testproject={{ project.path }} --architecture={% if platform.name == "switch" %}arm64{% else %}x64{% endif %} --scripting-backend=il2cpp --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --artifacts-path=artifacts --player-save-path=build/players --testfilter="Unity.Netcode.RuntimeTests.*" --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 + + variables: +# PS4 related + SCE_ORBIS_SDK_DIR: 'C:\Users\bokken\SCE\ps4_sdk_12_00' +# PS5 related + SCE_PROSPERO_SDK_DIR: 'C:\Program Files (x86)\SCE\Prospero SDKs\9.000' + SHADER_COMPILER_PATH: '${SCE_PROSPERO_SDK_DIR}\target\bins' + SCE_ROOT_DIR: 'C:\Program Files (x86)\SCE' +# Switch related + NINTENDO_SDK_ROOT: 'C:\Nintendo\nx_sdk-18_3_0\NintendoSDK' + UNITY_NINTENDOSDK_CLI_TOOLS: '${NINTENDO_SDK_ROOT}\Tools\CommandLineTools' + artifacts: + players: + paths: + - "build/players/**/*" + logs: + paths: + - "artifacts/**/*" +{% endfor -%} +{% endfor -%} +{% endfor -%} + + + +# Executes RuntimeTests of the NGO package in the Standalone build for consoles +{% for project in projects.default -%} +{% for platform in test_platforms.console_test -%} +{% for editor in validation_editors.all -%} +run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }}: + name: Run {{ project.name }} Tests - [{{ platform.name }}, {{ editor }}, il2cpp] + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} +{% endif %} + commands: + # Installing tools. + - sudo pip install unity-downloader-cli + - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp -c {{ platform.name }} --fast --wait + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat + + # Platform specific Execution + - utr --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --artifacts-path=test-results --player-load-path=build/players --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 + + variables: +# PS4 related + SCE_ORBIS_SDK_DIR: 'C:\Users\bokken\SCE\ps4_sdk_12_00' +# PS5 related + SCE_PROSPERO_SDK_DIR: 'C:\Program Files (x86)\SCE\Prospero SDKs\9.000' + SHADER_COMPILER_PATH: '${SCE_PROSPERO_SDK_DIR}\target\bins' + SCE_ROOT_DIR: 'C:\Program Files (x86)\SCE' +# Switch related + NINTENDO_SDK_ROOT: 'C:\Nintendo\nx_sdk-18_3_0\NintendoSDK' + UNITY_NINTENDOSDK_CLI_TOOLS: '${NINTENDO_SDK_ROOT}\Tools\CommandLineTools' + artifacts: + logs: + paths: + - "test-results/**/*" + dependencies: + - .yamato/console-standalone-test.yml#build_{{ project.name }}_{{ platform.name }}_{{ editor }} +{% endfor -%} +{% endfor -%} +{% endfor -%} \ No newline at end of file diff --git a/.yamato/desktop-standalone-tests.yml b/.yamato/desktop-standalone-tests.yml new file mode 100644 index 0000000000..b12c71f2eb --- /dev/null +++ b/.yamato/desktop-standalone-tests.yml @@ -0,0 +1,107 @@ +{% metadata_file .yamato/project.metafile %} +--- + +# Builds a player on desktop standalone platform and executes RuntimeTests (equivalent to PlayMode tests) of the NGO package in the Standalone build. +# Default project (testptoject) in this case is used as a context. +# Builds are made on each desktop platform as in project.metafile declaration +# Builds are made on each supported editor as in project.metafile declaration +# Builds are made with different scripting backends as in project.metafile declaration +# ARM64 architectures are for now not considered since Windows_arm64 is recommended to use only after builds (would require separation here) and when it comes to macOS_arm64 there is problem with OpenCL not being available + +# Build phase +{% for project in projects.default -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +{% for backend in scripting_backends -%} +desktop_standalone_build_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ editor }}: + name : Standalone Build - NGO {{ project.name }} - [{{ platform.name }}, {{ editor }}, {{ backend }}] + agent: + type: {% if platform.name == "mac" %} {{ platform.type }} {% else %} {{ platform.type }}::GPU {% endif %} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + commands: +# Installing tools +{% if platform.name == "ubuntu" %} + - sudo apt-get update -q + - sudo apt install -qy imagemagick +{% endif %} + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + +# Platform specific UTR setup + - | +{% if platform.name == "win" %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat +{% else %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr +{% endif %} + +# Installing editor + - unity-downloader-cli -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} --fast --wait + +# Build Player + - | +{% if platform.name == "win" %} + utr.bat --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --testproject={{ project.path }} --scripting-backend={{ backend }} --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --artifacts-path=artifacts --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 +{% else %} + ./utr --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --testproject={{ project.path }} --scripting-backend={{ backend }} --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --artifacts-path=artifacts --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 +{% endif %} + + artifacts: + players: + paths: + - "build/players/**/*" + logs: + paths: + - "artifacts/**/*" + + dependencies: + - .yamato/project-pack.yml#project_pack_-_{{ project.name }}_{{ platform.name }} +{% endfor -%} +{% endfor -%} +{% endfor -%} +{% endfor -%} + + + + +# Run phase +{% for project in projects.default -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +{% for backend in scripting_backends -%} +desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ editor }}: + name : Standalone Test - NGO {{ project.name }} - [{{ platform.name }}, {{ editor }}, {{ backend }}] + agent: + type: {% if platform.name == "mac" %} {{ platform.type }} {% else %} {{ platform.type }}::GPU {% endif %} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + commands: +# Platform specific UTR setup + - | +{% if platform.name == "win" %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat +{% else %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr +{% endif %} + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + + - unity-downloader-cli -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} --fast --wait + +# Run Standalone tests + - | +{% if platform.name == "win" %} + utr.bat --suite=playmode --player-load-path=build/players --artifacts-path=test-results --testproject={{ project.path }} --editor-location=.Editor --playergraphicsapi=Null --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 +{% else %} + ./utr --suite=playmode --player-load-path=build/players --artifacts-path=test-results --testproject={{ project.path }} --editor-location=.Editor --playergraphicsapi=Null --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 +{% endif %} + + artifacts: + logs: + paths: + - "test-results/**/*" + dependencies: + - .yamato/desktop-standalone-tests.yml#desktop_standalone_build_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ editor }} +{% endfor -%} +{% endfor -%} +{% endfor -%} +{% endfor -%} diff --git a/.yamato/disable-burst-if-requested.py b/.yamato/disable-burst-if-requested.py index fa53669944..edf84d3f26 100644 --- a/.yamato/disable-burst-if-requested.py +++ b/.yamato/disable-burst-if-requested.py @@ -1,3 +1,7 @@ +# This file was used before for mobiles and Webgl but was removed (around end of January 2025). The file itself was left here for now in case we would need to use it. +# This Python script is used to manage Burst AOT (Ahead-Of-Time) compilation settings for Unity builds. +# An example usage would be "- python .yamato/disable-burst-if-requested.py --project-path {{ project.path }} --platform WebGL" + import argparse import json import os diff --git a/.yamato/mobile-build-and-test.yml b/.yamato/mobile-build-and-test.yml deleted file mode 100644 index b3d16e097b..0000000000 --- a/.yamato/mobile-build-and-test.yml +++ /dev/null @@ -1,153 +0,0 @@ -{% metadata_file .yamato/project.metafile %} ---- - -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} -build_{{ project.name }}_tests_{{ editor }}_android: - name: Build {{ project.name }} Tests - {{ editor }} - Android - agent: - type: Unity::VM - image: desktop/android-execution-r19:v0.1.1-860408 - flavor: b1.xlarge - commands: - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat - - python .yamato/disable-burst-if-requested.py --project-path testproject --platform Android - - unity-downloader-cli -u {{ editor }} -c editor -c Android -w --fast - - | - set UTR_VERSION=0.12.0 - utr.bat --artifacts_path=artifacts --timeout=1800 --testproject={{ project.name }} --editor-location=.Editor --suite=playmode --platform=android --build-only --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --testfilter=Unity.Netcode.RuntimeTests - artifacts: - logs: - paths: - - '*.log' - - '*.xml' - - artifacts/**/* - - testproject/Logs/** - - testproject/Library/*.log - - testproject/*.log - - testproject/Builds/*.log - - build/test-results/** - - artifacts/** - - build/players/** - variables: - CI: true - ENABLE_BURST_COMPILATION: False -{% endfor -%} -{% endif -%} -{% endfor -%} - - -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} -build_{{ project.name }}_tests_{{ editor }}_iOS: - name: Build {{ project.name }} Tests - {{ editor }} - iOS - agent: - type: Unity::VM::osx - image: mobile/ios-macos-11:stable - flavor: b1.large - commands: - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - unity-downloader-cli -u {{ editor }} -c editor -c iOS -w --fast - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr - - chmod +x ./utr - - export UTR_VERSION=0.12.0 - - ./utr --artifacts_path=artifacts --timeout=1800 --testproject={{ project.name }} --editor-location=.Editor --suite=playmode --platform=iOS --build-only --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --testfilter=Unity.Netcode.RuntimeTests - artifacts: - logs: - paths: - - '*.log' - - '*.xml' - - artifacts/**/* - - testproject/Logs/** - - testproject/Library/*.log - - testproject/*.log - - testproject/Builds/*.log - - build/test-results/** - - artifacts/** - - build/players/** -{% endfor -%} -{% endif -%} -{% endfor -%} - - -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} -run_{{ project.name }}_tests_{{ editor }}_iOS: - name: Run {{ project.name }} Tests - {{ editor }} - iOS - agent: - type: Unity::mobile::iPhone - model: SE - image: mobile/ios-macos-11:stable - flavor: b1.medium - # Set a dependency on the build job - dependencies: - - .yamato/mobile-build-and-test.yml#build_{{ project.name }}_tests_{{ editor }}_iOS - commands: - # Download standalone UnityTestRunner - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr - # Give UTR execution permissions - - chmod +x ./utr - # Run the test build on the device - - export UTR_VERSION=0.12.0 - - ./utr -artifacts_path=artifacts --testproject={{ project.name }} --editor-location=.Editor --reruncount=2 --suite=playmode --platform=iOS --player-load-path=build/players --testfilter=Unity.Netcode.RuntimeTests - artifacts: - logs: - paths: - - '*.log' - - '*.xml' - - artifacts/**/* - - testproject/Logs/** - - testproject/Library/*.log - - testproject/*.log - - testproject/Builds/*.log - - build/test-results/** - - artifacts/** - - build/players/** -{% endfor -%} -{% endif -%} -{% endfor -%} - - -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} -run_{{ project.name }}_tests_{{ editor }}_android: - name: Run {{ project.name }} Tests - {{ editor }} - Android - agent: - type: Unity::mobile::shield - image: mobile/android-execution-r19:stable - flavor: b1.medium - # Skip repository cloning - skip_checkout: true - # Set a dependency on the build job - dependencies: - - .yamato/mobile-build-and-test.yml#build_{{ project.name }}_tests_{{ editor }}_android - commands: - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat - - | - set ANDROID_DEVICE_CONNECTION=%BOKKEN_DEVICE_IP% - start %ANDROID_SDK_ROOT%\platform-tools\adb.exe connect %BOKKEN_DEVICE_IP% - start %ANDROID_SDK_ROOT%\platform-tools\adb.exe devices - set UTR_VERSION=0.12.0 - ./utr --artifacts_path=artifacts --testproject={{ project.name }} --editor-location=.Editor --reruncount=2 --suite=playmode --platform=android --player-connection-ip=%BOKKEN_HOST_IP% --player-load-path=build/players --testfilter=Unity.Netcode.RuntimeTests - # Set uploadable artifact paths - artifacts: - logs: - paths: - - '*.log' - - '*.xml' - - artifacts/**/* - - testproject/Logs/** - - testproject/Library/*.log - - testproject/*.log - - testproject/Builds/*.log - - build/test-results/** - - artifacts/** - - build/players/** -{% endfor -%} -{% endif -%} -{% endfor -%} diff --git a/.yamato/mobile-standalone-test.yml b/.yamato/mobile-standalone-test.yml new file mode 100644 index 0000000000..2ca2444b86 --- /dev/null +++ b/.yamato/mobile-standalone-test.yml @@ -0,0 +1,106 @@ +{% metadata_file .yamato/project.metafile %} +--- + +# Builds a player on mobile standalone platform and executes RuntimeTests (equivalent to PlayMode tests) of the NGO package in the Standalone build. +# Default project (testptoject) in this case is used as a context. +# Builds/Tests are made on each mobile platform (Android and iOS) as in project.metafile declaration +# Builds/Tests are made on each supported editor as in project.metafile declaration + +# For mobile devices it's necessary to split build and run phases +# For iOS we need to use il2cpp. For android we could use both but il2cpp is recommended so for now we will only use il2cpp as scripting backend +# iOS works only with ARM64 and Android is tested with both ARM64 and ARMv7 + +# Builds are made with x64 architecture machines since those are compatible to run on ARM64 devices +{% for project in projects.default -%} +{% for platform in test_platforms.mobile_build -%} +{% for editor in validation_editors.all -%} +build_{{ project.name }}_{{ platform.name }}_{{ editor }}: + name: Build {{ project.name }} - [{{ platform.name }}, {{ editor }}, il2cpp] + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + commands: + # Installing tools + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + + # Installing editor + - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp {% if platform.base == "mac" %} -c ios {% else %} -c android {% endif %} --fast --wait + + # Platform specific Build +{% if platform.base == "win" %} + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat + - utr.bat --suite=playmode --platform={{ platform.standalone }} --testproject={{ project.path }} --architecture={{ platform.architecture }} --scripting-backend=il2cpp --editor-location=.Editor --artifacts-path=artifacts --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 +{% else %} + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr + - chmod +x ./utr + - ./utr --suite=playmode --platform={{ platform.standalone }} --testproject={{ project.path }} --architecture={{ platform.architecture }} --scripting-backend=il2cpp --editor-location=.Editor --artifacts-path=artifacts --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 +{% endif %} + artifacts: + players: + paths: + - "build/players/**/*" + logs: + paths: + - "artifacts/**/*" + + variables: + CI: true + ENABLE_BURST_COMPILATION: False +{% endfor -%} +{% endfor -%} +{% endfor -%} + + +# Executes RuntimeTests of the NGO package in the Standalone build for mobiles +{% for project in projects.default -%} +{% for platform in test_platforms.mobile_test -%} +{% for editor in validation_editors.all -%} +run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }}: + name: Run {{ project.name }} Tests - [{{ platform.name }}, {{ editor }}, il2cpp] + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} +{% endif %} + # Skip repository cloning + skip_checkout: true + commands: + # Installing tools + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + + # Installing editor. We still need the editor to run tests on standalone build + - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp {% if platform.base == "mac" %} -c ios {% else %} -c android {% endif %} --fast --wait + +{% if platform.standalone == "Android" %} + # Download standalone UnityTestRunner and ADB setup + - command: curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat + - command: wget http://artifactory-slo.bf.unity3d.com/artifactory/mobile-generic/android/ADBKeys.zip!/adbkey.pub -O %USERPROFILE%/.android/adbkey.pub + - command: wget http://artifactory-slo.bf.unity3d.com/artifactory/mobile-generic/android/ADBKeys.zip!/adbkey -O %USERPROFILE%/.android/adbkey + - command: gsudo NetSh Advfirewall set allprofiles state off + + # Connect to Android device + - command: '"%ANDROID_SDK_ROOT%\platform-tools\adb.exe" connect %BOKKEN_DEVICE_IP%' + + # Run tests + - | + set ANDROID_DEVICE_CONNECTION=%BOKKEN_DEVICE_IP% + utr --suite=playmode --platform={{ platform.standalone }} --artifacts-path=test-results --player-load-path=build/players --testproject={{ project.path }} --editor-location=.Editor --player-connection-ip=%BOKKEN_HOST_IP% --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=3600 +{% else %} + # Download standalone UnityTestRunner + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr + + # Run tests + - ./utr --suite=playmode --platform={{ platform.standalone }} --artifacts-path=test-results --player-load-path=build/players --testproject={{ project.path }} --editor-location=.Editor --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=3600 +{% endif %} + artifacts: + logs: + paths: + - "test-results/**/*" + dependencies: + - .yamato/mobile-standalone-test.yml#build_{{ project.name }}_{{ platform.name }}_{{ editor }} +{% endfor -%} +{% endfor -%} +{% endfor -%} diff --git a/.yamato/multiprocess-project-tests.yml b/.yamato/multiprocess-project-tests.yml deleted file mode 100644 index 3505d72c52..0000000000 --- a/.yamato/multiprocess-project-tests.yml +++ /dev/null @@ -1,42 +0,0 @@ -{% metadata_file .yamato/project.metafile %} ---- - -{% for project in projects -%} -{% if project.name == "testproject" %} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} -singlenode_multiprocess_test_testproject_{{ editor }}_{{ platform.name }}: - name : Multiprocess Tests - {{ editor }} on {{ platform.name }} - agent: - type: {{ platform.type }} - image: {{ platform.image }} - flavor: {{ platform.flavor}} - commands: - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple -{% if editor != "trunk" %} - - unity-downloader-cli -u {{ editor }} -c editor -w --fast - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr{% if platform.name == "win" %}.bat --output utr.bat{% endif %}{% if platform.name != "win" %} --output utr && chmod +x ./utr{% endif %} - - {{ platform.editorpath }} -projectpath testproject -batchmode -quit -nographics -logfile BuildMultiprocessTestPlayer.log -executeMethod Unity.Netcode.MultiprocessRuntimeTests.BuildMultiprocessTestPlayer.BuildRelease -{% if platform.name == "mac" %} - sudo codesign --force --deep --sign - ./testproject/Builds/MultiprocessTests/MultiprocessTestPlayer.app{% endif %} - - {{ platform.utr }} --suite=playmode --testproject=testproject --editor-location=.Editor --testfilter=Unity.Netcode.MultiprocessRuntimeTests --extra-editor-arg=-bypassIgnoreUTR -{% endif %} - after: - - echo "After block" -{% if editor != "trunk" %} -{% if platform.name == "win" %} - copy %USERPROFILE%\.multiprocess\logfile* .{% endif %} -{% if platform.name != "win" %} - cp $HOME/.multiprocess/logfile* .{% endif %} -{% endif %} - artifacts: - logs: - paths: - - "upm-ci~/test-results/**/*" - - BuildMultiprocessTestPlayer.log - - "logfile*" - - "*.log" - - "*.txt" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} -{% endfor -%} -{% endfor -%} -{% endif -%} -{% endfor -%} diff --git a/.yamato/package-pack.yml b/.yamato/package-pack.yml new file mode 100644 index 0000000000..cc85801042 --- /dev/null +++ b/.yamato/package-pack.yml @@ -0,0 +1,24 @@ +{% metadata_file .yamato/project.metafile %} +--- + +# Packs Netcode for GameObjects together with performing initial checks. +# For this job no specific platform support and no running Unity instance is required so small agent (as per project.metafile definition) could be used to save resources and speed up the process. +# If everyone adheres to this rule it can create bottlenecks (since everyone would use this machine) so we decided to pack with the same platform as the given job runs on +{% for platform in test_platforms.desktop -%} +package_pack_-_ngo_{{ platform.name }}: + name: Package Pack (and x-ray) - NGO [{{ platform.name }}] + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + timeout: 0.25 + commands: + - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.it.unity3d.com/artifactory/api/npm/upm-npm + - upm-ci package pack --package-path com.unity.netcode.gameobjects + - upm-pvp xray --packages "upm-ci~/packages/*.tgz" --results upm-ci~/xray + - upm-pvp require "supported rme com.unity.netcode.gameobjects/pvpExceptions.json" --allow-missing --results upm-ci~/xray --exemptions upm-ci~/xray/new-exemptions.json + artifacts: + packages: + paths: + - "upm-ci~/**" +{% endfor -%} \ No newline at end of file diff --git a/.yamato/package-tests.yml b/.yamato/package-tests.yml index 1779a7008e..0b3eb31109 100644 --- a/.yamato/package-tests.yml +++ b/.yamato/package-tests.yml @@ -1,57 +1,26 @@ {% metadata_file .yamato/project.metafile %} --- -# Go through all platforms, editors and packages in the metadata -# to generate its independent package tests and validation tests. -# The idea is to only run validation once per package and not mix. -# the results with package tests -{% for project in projects -%} -{% for package in project.packages -%} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} -test_{{project.name}}_{{ package.name }}_{{ editor }}_{{ platform.name }}: - name : {{ project.name }} - {{ package.name }} package tests - {{ editor }} on {{ platform.name }} +# Executes PlayMode and EditMode tests of the NGO package in the Editor context +# Those tests run in the editor so we don't need to consider different scripting backends or architectures +# Tests are executed for all supported editors on each desktop platform as in project.metafile declaration +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +package_test_-_ngo_{{ editor }}_{{ platform.name }}: + name : Package Test - NGO [{{ platform.name }}, {{ editor }}] agent: type: {{ platform.type }} image: {{ platform.image }} - flavor: {{ platform.flavor}} + flavor: {{ platform.flavor }} commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %}upm-ci project test -u {{ editor }} --type package-tests --project-path {{ project.name }} --package-filter {{ package.name }} + - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait + - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %}upm-ci package test -u {{ editor }} --package-path com.unity.netcode.gameobjects --type package-tests --extra-utr-arg="--extra-editor-arg=-testCategory --extra-editor-arg=!Performance --reruncount=1 --clean-library-on-rerun" artifacts: logs: paths: - "upm-ci~/test-results/**/*" dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} + - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} {% endfor -%} -{% endfor -%} -{% endfor -%} -{% endfor -%} - -# Test minimal project with different versions of dependencies -{% for project in projects -%} -{% if project.name == "minimalproject" -%} -{% for dependency in dependencies -%} -{% for editor in dependency.test_editors -%} -{% for platform in test_platforms -%} -test_compatibility_{{project.name}}_{{ project.packages.first.name }}_with_{{ dependency.name }}@{{ dependency.version }}_{{ editor }}_{{ platform.name }}: - name : {{ project.name }} - {{ project.packages.first.name }} with {{ dependency.name }}@{{ dependency.version }} - {{ editor }} on {{ platform.name }} - agent: - type: {{ platform.type }} - image: {{ platform.image }} - flavor: {{ platform.flavor}} - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %}upm-ci project test -u {{ editor }} --type project-tests --project-path {{ project.name }} --package-filter {{ project.packages.first.name }} - artifacts: - logs: - paths: - - "upm-ci~/test-results/**/*" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }}_{{ dependency.name }}@{{ dependency.version }} -{% endfor -%} -{% endfor -%} -{% endfor -%} -{% endif -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/performance-tests.yml b/.yamato/performance-tests.yml new file mode 100644 index 0000000000..abdbef444e --- /dev/null +++ b/.yamato/performance-tests.yml @@ -0,0 +1,55 @@ +{% metadata_file .yamato/project.metafile %} +--- + +# Performance tests for the `com.unity.netcode.gameobjects` package. No performance data will be reported. +# Performance tests are executed within editor on desktop platforms in context of default project (testproject) +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +{% for project in projects.default -%} +performance_editor_tests_-_NGO_{{ platform.name }}_{{ editor }}_no_data_reporting: + name : Performance editor Tests - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}] (No Data Reporting) + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + commands: + # Installing tools +{% if platform.name == "ubuntu" %} + - sudo apt-get update -q + - sudo apt install -qy imagemagick +{% endif %} + - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple + + # Platform specific UTR setup +{% if platform.name == "win" %} + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat +{% else %} + - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr +{% endif %} + + # Installing editor + - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait + + # Build Player + - | +{% if platform.name == "win" %} + utr.bat --suite=editor --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --timeout=3600 --artifacts-path=artifacts --extra-editor-arg=-assemblyNames --extra-editor-arg=Unity.NetCode.* --extra-editor-arg=-testCategory --extra-editor-arg=Performance --extra-editor-arg=-enablePackageManagerTraces --reruncount=1 --clean-library-on-rerun --dontreportperformancedata +{% else %} + ./utr --suite=editor --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --timeout=3600 --artifacts-path=artifacts --extra-editor-arg=-assemblyNames --extra-editor-arg=Unity.NetCode.* --extra-editor-arg=-testCategory --extra-editor-arg=Performance --extra-editor-arg=-enablePackageManagerTraces --reruncount=1 --clean-library-on-rerun --dontreportperformancedata +{% endif %} + + artifacts: + logs: + paths: + - '*.log' + - '*.xml' + - artifacts/**/* + - {{ project.name }}/Logs/** + - {{ project.name }}/Library/*.log + - {{ project.name }}/*.log + - {{ project.name }}/Builds/*.log + - build/test-results/** +{% endfor -%} +{% endfor -%} +{% endfor -%} \ No newline at end of file diff --git a/.yamato/project-pack.yml b/.yamato/project-pack.yml index bd6c71e7d4..0963d99271 100644 --- a/.yamato/project-pack.yml +++ b/.yamato/project-pack.yml @@ -1,44 +1,23 @@ {% metadata_file .yamato/project.metafile %} --- -{% for project in projects -%} -pack_{{ project.name }}: - name: Pack {{ project.name }} - agent: - type: Unity::VM - image: package-ci/ubuntu-22.04:v4 - flavor: b1.small - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci project pack --project-path {{ project.path }} - artifacts: - packages: - paths: - - "upm-ci~/packages/**/*" -{% endfor -%} -# Pack minimal project with different versions of dependencies -{% for project in projects -%} -{% if project.name == "minimalproject" -%} -{% for dependency in dependencies -%} -pack_{{ project.name }}_{{ dependency.name }}@{{ dependency.version }}: - name: Pack {{ project.name }} with {{ dependency.name }}@{{ dependency.version }} +# Jobs that iterate through and packs all NGO projects listed (to use in different jobs) +# For this job no specific platform support and no running Unity instance is required so small agent (as per project.metafile definition) coul be used to save resources and speed up the process +# If everyone adheres to this rule it can create bottlenecks (since everyone would use this machine) so we decided to pack project with the same platform as the given job runs on +{% for project in projects.all -%} +{% for platform in test_platforms.desktop -%} +project_pack_-_{{ project.name }}_{{ platform.name }}: + name: Project Pack - {{ project.name }} [{{ platform.name }}] agent: - type: Unity::VM - image: package-ci/ubuntu-22.04:v4 - flavor: b1.small + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - curl -L https://artifactory.prd.it.unity3d.com/artifactory/api/gpg/key/public | sudo apt-key add - - - sudo sh -c "echo 'deb https://artifactory.prd.it.unity3d.com/artifactory/unity-apt-local bionic main' > /etc/apt/sources.list.d/unity.list" - - sudo apt update - - sudo apt install -y unity-config - - unity-config settings project-path {{ project.path }} - - unity-config project add dependency {{ dependency.name }}@{{ dependency.version }} + - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - upm-ci project pack --project-path {{ project.path }} artifacts: packages: paths: - "upm-ci~/packages/**/*" {% endfor -%} -{% endif -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/project-promotion.yml b/.yamato/project-promotion.yml deleted file mode 100644 index 8858dbd382..0000000000 --- a/.yamato/project-promotion.yml +++ /dev/null @@ -1,74 +0,0 @@ -{% metadata_file .yamato/project.metafile %} ---- -{% for project in projects -%} -{% if project.publish == "true" -%} -{% for package in project.packages -%} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} -promotion_validate_{{ project.name }}_{{ package.name }}_{{ platform.name }}_{{ editor }}: - name : Validate (Vetting Tests) Project {{ project.name }} - Package {{ package.name }} - {{ editor }} on {{ platform.name }} - agent: - type: {{ platform.type }} - image: {{ platform.image }} - flavor: {{ platform.flavor}} - variables: - UPMCI_PROMOTION: 1 - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %}upm-ci project test -u {{ editor }} --project-path {{ project.path }} --type vetting-tests --project-path {{ project.path }} --package-filter {{ package.name }} - artifacts: - logs: - paths: - - "upm-ci~/test-results/**/*" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} -{% endfor -%} -{% endfor -%} - -promote_{{ project.name }}_{{ package.name }}: - name: Promote Project {{ project.name }} - Package {{ package.name }} to Production - agent: - type: Unity::VM - image: package-ci/win10:v4 - flavor: b1.small - variables: - UPMCI_PROMOTION: 1 - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci package promote --package-path {{ package.path }} - artifacts: - artifacts: - paths: - - "upm-ci~/packages/*.tgz" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} - - .yamato/project-promotion.yml#promotion_validate_{{ project.name }}_{{ package.name }}_{{ test_platforms.first.name }}_{{ validation_editor }} - - .yamato/project-promotion.yml#promote_{{ project.name }}_{{ package.name }}_dry_run - -promote_{{ project.name }}_{{ package.name }}_dry_run: - name: Promote Project {{ project.name }} - Package {{ package.name }} to Production (dry-run) - agent: - type: Unity::VM - image: package-ci/win10:v4 - flavor: b1.small - variables: - UPMCI_PROMOTION: 1 - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci package promote --package-path {{ package.path }} --dry-run - artifacts: - artifacts: - paths: - - "upm-ci~/packages/*.tgz" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} - - .yamato/project-publish.yml#publish_{{ project.name }}_{{ package.name }} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} - - .yamato/project-promotion.yml#promotion_validate_{{ project.name }}_{{ package.name }}_{{ platform.name }}_{{ editor }} -{% endfor -%} -{% endfor -%} - -{% endfor -%} -{% endif -%} -{% endfor -%} diff --git a/.yamato/project-publish.yml b/.yamato/project-publish.yml deleted file mode 100644 index 9f114a9ae6..0000000000 --- a/.yamato/project-publish.yml +++ /dev/null @@ -1,67 +0,0 @@ -{% metadata_file .yamato/project.metafile %} ---- -{% for project in projects -%} -{% if project.publish == "true" -%} -{% for package in project.packages -%} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} -validate_{{ package.name }}_{{ platform.name }}_{{ editor }}: - name : Validate (Isolation Tests) Package {{ package.name }} - {{ editor }} on {{ platform.name }} - agent: - type: {{ platform.type }} - image: {{ platform.image }} - flavor: {{ platform.flavor}} - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %}upm-ci project test -u {{ editor }} --type isolation-tests --project-path {{ project.path }} --package-filter {{ package.name }} --platform editmode - artifacts: - logs: - paths: - - "upm-ci~/test-results/**/*" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} -{% endfor -%} -{% endfor -%} - -publish_{{ project.name }}_{{ package.name }}: - name: Publish Project {{project.name }} - Package {{ package.name }} to Internal Registry - agent: - type: Unity::VM - image: package-ci/win10:v4 - flavor: b1.large - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci package publish --package-path {{ package.path }} - artifacts: - artifacts: - paths: - - "upm-ci~/packages/*.tgz" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} - - .yamato/project-publish.yml#validate_{{ package.name }}_{{ test_platforms.first.name }}_{{ validation_editor }} - - .yamato/project-publish.yml#publish_{{ project.name }}_{{ package.name }}_dry_run - -publish_{{ project.name }}_{{ package.name }}_dry_run: - name: Publish Project {{project.name }} - Package {{ package.name }} to Internal Registry (dry-run) - agent: - type: Unity::VM - image: package-ci/win10:v4 - flavor: b1.large - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci package publish --package-path {{ package.path }} --dry-run - artifacts: - artifacts: - paths: - - "upm-ci~/packages/*.tgz" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} - - .yamato/project-publish.yml#validate_{{ package.name }}_{{ platform.name }}_{{ editor }} -{% endfor -%} -{% endfor -%} - -{% endfor -%} -{% endif -%} -{% endfor -%} diff --git a/.yamato/project-standards.yml b/.yamato/project-standards.yml index a5147a9dce..b0a74ad6a6 100644 --- a/.yamato/project-standards.yml +++ b/.yamato/project-standards.yml @@ -1,15 +1,30 @@ {% metadata_file .yamato/project.metafile %} --- -standards_{{ projects.first.name }}: - name: Standards Check {{ projects.first.name }} + +# Project standards are being checked for package (in project context). +# It should be enough to perform the test on default project (testproject) but to make sure that all projects conform to our standards job for each of them will be available. +# Tests are executed for default editor (trunk) on default platform (ubuntu) as in project.metafile declaration since results would be the same across editors and platforms. +{% for project in projects.all -%} +{% for platform in test_platforms.default -%} +{% for editor in validation_editors.default -%} +standards_{{ platform.name }}_{{ project.name }}_{{ editor }}: + name: Standards Check - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}] agent: - type: Unity::VM - image: package-ci/ubuntu-20.04:v4 - flavor: b1.large + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} commands: + # .NET environment setup - dotnet --version - dotnet format --version + + # Unity setup - pip install unity-downloader-cli --upgrade --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - unity-downloader-cli -u {{ projects.first.test_editors.first }} -c editor --wait --fast - - .Editor/Unity -batchmode -nographics -logFile - -executeMethod Packages.Rider.Editor.RiderScriptEditor.SyncSolution -projectPath {{ projects.first.path }} -quit - - dotnet run --project dotnet-tools/netcode.standards -- --project={{ projects.first.path }} --check + - unity-downloader-cli -u {{ editor }} -c editor --wait --fast + - .Editor/Unity -batchmode -nographics -logFile - -executeMethod Packages.Rider.Editor.RiderScriptEditor.SyncSolution -projectPath {{ project.path }} -quit + + # Standards check + - dotnet run --project=dotnet-tools/netcode.standards -- --project={{ project.path }} --check +{% endfor -%} +{% endfor -%} +{% endfor -%} \ No newline at end of file diff --git a/.yamato/project-tests.yml b/.yamato/project-tests.yml index 1737ec363e..ec91e04540 100644 --- a/.yamato/project-tests.yml +++ b/.yamato/project-tests.yml @@ -1,31 +1,31 @@ {% metadata_file .yamato/project.metafile %} --- -# For every platform and editor version, run its project tests without -# running package tests too since they are handled on their respective -# jobs -{% for project in projects -%} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} -test_{{ project.name }}_{{ editor }}_{{ platform.name }}: - name : {{ project.name }} project tests - {{ editor }} on {{ platform.name }} +# Executes PlayMode and EditMode tests of the given project (ones that have tests) in the Editor context +# Those tests don't include NGO package tests since they are handled on their respective jobs. +# Those tests run in the editor so we don't need to consider different scripting backends or architectures +# Tests are executed for all supported editors on each desktop platform as in project.metafile declaration +{% for project in projects.all -%} +{% if project.has_tests == "true" -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +test_{{ project.name }}_{{ platform.name }}_{{ editor }}: + name : Project Test - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}] agent: type: {{ platform.type }} image: {{ platform.image }} - flavor: {{ platform.flavor}} + flavor: {{ platform.flavor }} commands: - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - unity-downloader-cli -u {{ editor }} -c editor -w --fast - - {% if platform.name == "ubuntu" %}DISPLAY=:0 && {% endif %}upm-ci project test -u {{ editor }} --project-path {{ project.path }} --type project-tests + - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait + - {% if platform.name == "ubuntu" %}DISPLAY=:0 && {% endif %}upm-ci project test -u {{ editor }} --project-path {{ project.path }} --type project-tests --extra-utr-arg="--extra-editor-arg=-testCategory --extra-editor-arg=!Performance --reruncount=1 --clean-library-on-rerun" artifacts: logs: paths: - "upm-ci~/test-results/**/*" dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} - -{% endfor -%} + - .yamato/project-pack.yml#project_pack_-_{{ project.name }}_{{ platform.name }} {% endfor -%} {% endfor -%} - +{% endif -%} +{% endfor -%} \ No newline at end of file diff --git a/.yamato/project-updated-dependencies-test.yml b/.yamato/project-updated-dependencies-test.yml index bd39d49db0..513087f3c4 100644 --- a/.yamato/project-updated-dependencies-test.yml +++ b/.yamato/project-updated-dependencies-test.yml @@ -1,59 +1,27 @@ {% metadata_file .yamato/project.metafile %} --- -# Go through all platforms, editors and packages in the metadata -# to generate its independent package tests and validation tests. -# The idea is to only run validation once per package and not mix. -# the results with package tests -{% for package in projects.first.packages -%} -{% for editor in projects.first.test_editors -%} -{% for platform in test_platforms -%} -dependency_{{ projects.first.name }}_{{ package.name }}_{{ platform.name }}_{{ editor }}: - name : Dependency Test of Project {{ projects.first.name }} - Package {{ package.name }} - {{ editor }} on {{ platform.name }} + +# Executes package tests for each package referenced in the project using the newest compatible version of the package dependencies. +# This is just to ensure that no critical change is made. It will be run on a default editor and default platform (it can be discussed if we should run it per all editors and platforms) +{% for project in projects.default -%} +{% for platform in test_platforms.default -%} +{% for editor in validation_editors.default -%} +updated-dependencies_{{ project.name }}_NGO_{{ platform.name }}_{{ editor }}: + name : Updated Dependencies Test - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}] agent: type: {{ platform.type }} image: {{ platform.image }} - flavor: {{ platform.flavor}} + flavor: {{ platform.flavor }} commands: - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci project test -u {{ editor }} --project-path {{ projects.first.path }} --type updated-dependencies-tests --package-filter {{ package.name }} + - upm-ci project test -u {{ editor }} --project-path {{ project.path }} --type updated-dependencies-tests --package-filter com.unity.netcode.gameobjects artifacts: logs: paths: - "upm-ci~/test-results/**/*" - triggers: - recurring: - - branch: master - frequency: 20 * * ? dependencies: - - path: .yamato/project-pack.yml#pack_{{ projects.first.name }} + - path: .yamato/project-pack.yml#project_pack_-_{{ project.name }}_{{ platform.name }} rerun: always - -{% endfor -%} -{% endfor -%} {% endfor -%} - -dependency_test_trigger_{{ projects.first.name }}: - name: Project {{ projects.first.name }} Dependency Tests Trigger - agent: - type: Unity::VM - image: package-ci/ubuntu-22.04:v4 - flavor: b1.small - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci project izon -d --project-path {{ projects.first.path }} - artifacts: - logs: - paths: - - "upm-ci~/test-results/**/*" - triggers: - recurring: - - branch: master - frequency: 22 * * ? - dependencies: -{% for package in projects.first.packages -%} -{% for editor in projects.first.test_editors -%} -{% for platform in test_platforms -%} - - .yamato/project-updated-dependencies-test.yml#dependency_{{ projects.first.name }}_{{ package.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} -{% endfor -%} \ No newline at end of file diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 191842fd5e..43c1202c90 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -1,80 +1,159 @@ -validation_editor: 6000.0 -mobile_validation_editor: 6000.0 +# The small agent was created to handle jobs that don't involve running Unity (for example pack job). +# It uses ubuntu since Linux VMs are faster and cheaper to provision than Mac or Windows Virtual Machines (VMs). +# The b1.small flavour is nearly always sufficient for jobs that don’t involve running Unity Editor. +small_agent_platform: + - name: ubuntu + type: Unity::VM + image: package-ci/ubuntu-22.04:v4 + flavor: b1.small -# Platforms that will be tested. The first entry in this array will also -# be used for validation +# Platforms that project will/can be tested on. test_platforms: - - name: win - type: Unity::VM - image: package-ci/win10:v4 - flavor: b1.large - pathsep: \ - standalone: StandaloneWindows64 - editorpath: .Editor\Unity.exe - utr: .\utr.bat - - name: mac - type: Unity::VM::osx - image: package-ci/macos-13:v4 - pathsep: / - standalone: StandaloneOSX - flavor: b1.large - editorpath: .Editor/Unity.app/Contents/MacOS/Unity - utr: ./utr - - name: ubuntu - type: Unity::VM - image: package-ci/ubuntu-22.04:v4 - flavor: b1.large - pathsep: / - standalone: StandaloneLinux64 - editorpath: .Editor/Unity - utr: ./utr - -# Projects within the repository that will be tested. Name will be used -# for job ids, so it should not contain spaces/non-supported characters -projects: - - name: testproject - path: testproject - validate: true - publish: true - has_tests: true - # Packages within a project that will be tested - packages: - - name: com.unity.netcode.gameobjects - path: com.unity.netcode.gameobjects - test_editors: - - 6000.0 - - trunk - - name: minimalproject - path: minimalproject - validate: false - publish: false - has_tests: false - packages: - - name: com.unity.netcode.gameobjects - path: com.unity.netcode.gameobjects - test_editors: - - 6000.0 - - name: testproject-tools-integration - path: testproject-tools-integration - validate: false - publish: false - has_tests: true - test_editors: - - 6000.0 - - trunk - -# Package dependencies -dependencies: - - name: com.unity.transport - version: 2.2.1 - test_editors: - - 6000.0 - - trunk - -# Scripting backends used by Standalone Playmode Tests + # Default platform is used for all basic jobs. Ubuntu was chosen since it's fastest and most available + default: + - name: ubuntu + type: Unity::VM + image: package-ci/ubuntu-22.04:v4 + flavor: b1.large + standalone: StandaloneLinux64 + desktop: + - name: ubuntu + type: Unity::VM + image: package-ci/ubuntu-22.04:v4 + flavor: b1.large + standalone: StandaloneLinux64 + model: rtx2080 + - name: win + type: Unity::VM + image: package-ci/win10:v4 + flavor: b1.large + standalone: StandaloneWindows64 + model: rtx2080 + - name: mac + type: Unity::VM::osx + image: package-ci/macos-13:v4 + flavor: m1.mac + standalone: StandaloneOSX + # For mobile devices there is a split between the build and run phase so there is a need of splitting specification for both + mobile_build: + - name: android + type: Unity::VM + image: package-ci/win10:v4 + flavor: b1.large + standalone: Android + base: win + architecture: armv7 + # iOS modern builds are ARM64-only, thus no testing with armv7 (as in android case) + - name: ios-arm64 + type: Unity::VM::osx + image: package-ci/macos-13:v4 + flavor: m1.mac + standalone: IOS + base: mac + architecture: arm64 + mobile_test: + - name: android + type: Unity::mobile::shield + image: package-ci/win10:v4 + flavor: b1.large + standalone: Android + base: win + architecture: armv7 + - name: ios-arm64 + type: Unity::mobile::iPhone + image: package-ci/macos-13-arm64:v4 + flavor: m1.mac + model: SE + standalone: IOS + base: mac + architecture: arm64 + console_build: + - name: ps4 + type: Unity::VM + image: package-ci/win10-ps4:v4 + flavor: b1.large + standalone: PS4 + - name: ps5 + type: Unity::VM + image: package-ci/win10-ps5:v4 + flavor: b1.large + standalone: PS5 + - name: switch + type: Unity::VM + image: package-ci/win10-switch:v4 + flavor: b1.large + standalone: Switch + - name: GameCoreXboxOne + type: Unity::VM + image: package-ci/win10-xbox:v4 + flavor: b1.large + standalone: GameCoreXboxOne + - name: GameCoreScarlett + type: Unity::VM + image: package-ci/win10-xbox:v4 + flavor: b1.large + standalone: GameCoreScarlett + console_test: + - name: ps4 + type: Unity::console::ps4 + image: package-ci/win10-ps4:v4 + flavor: b1.large + standalone: PS4 + - name: ps5 + type: Unity::console::ps5 + image: package-ci/win10-ps5:v4 + flavor: b1.large + standalone: PS5 + - name: switch + type: Unity::console::switch + image: package-ci/win10-switch:v4 + flavor: b1.large + standalone: Switch + base: win + - name: GameCoreXboxOne + type: Unity::console::xbox + image: package-ci/win10-xbox:v4 + flavor: b1.large + standalone: GameCoreXboxOne + - name: GameCoreScarlett + type: Unity::console::scarlett + image: package-ci/win10-xbox:v4 + flavor: b1.large + standalone: GameCoreScarlett + + +# Editors to be used for testing. +# Since NGOv2 official support started from U6 it means that only those editors should be used for testing +validation_editors: + default: + - trunk + all: + - 6000.0 + - 6000.1 + - trunk + + +# Scripting backends used by Standalone RunTimeTests scripting_backends: - mono - il2cpp -# Images with build-tools installed required for Standalone Tests - IL2CPP -win_il2cpp_test_image: dots-player/windows10:latest + +# Projects within the repository that will be tested. Name will be used +# for job ids, so it should not contain spaces/non-supported characters +# The default project will also be used for validation, code coverage etc. +projects: + default: + - name: testproject + path: testproject + has_tests: true + all: + - name: testproject + path: testproject + has_tests: true + - name: minimalproject + path: minimalproject + has_tests: false + - name: testproject-tools-integration + path: testproject-tools-integration + has_tests: true \ No newline at end of file diff --git a/.yamato/standalone-project-tests.yml b/.yamato/standalone-project-tests.yml deleted file mode 100644 index abaefc753f..0000000000 --- a/.yamato/standalone-project-tests.yml +++ /dev/null @@ -1,40 +0,0 @@ -{% metadata_file .yamato/project.metafile %} ---- - -{% for project in projects -%} -{% if project.has_tests == "true" -%} -{% for editor in project.test_editors -%} -{% for platform in test_platforms -%} -{% for backend in scripting_backends -%} -standalone_tests_{{ project.name }}_{{ backend }}_{{ editor }}_{{ platform.name }}: - name : Standalone Tests - {{ project.name }} - [{{ backend }}, {{ platform.name }}, {{ editor }}] - agent: - type: {{ platform.type }}{% if platform.name == "ubuntu" %}::GPU{% endif %} -{% if platform.name == "ubuntu" %} model: rtx2080{% endif %} - image: {% if platform.name == 'win' and backend == 'il2cpp' %}{{ win_il2cpp_test_image }} {% else %} {{ platform.image }} {% endif %} - flavor: {{ platform.flavor}} - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr{% if platform.name == "win" %}.bat{% endif %} --output utr{% if platform.name == "win" %}.bat{% endif %} -{% if platform.name != "win" %} - - chmod +x ./utr -{% endif %} - - unity-downloader-cli -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp{% endif %} --fast --wait - - {% if platform.name != "win" %}./{% endif %}utr --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --testproject=testproject --player-save-path=build/players --artifacts_path=build/logs --scripting-backend={{ backend }} --build-only --testfilter=Unity.Netcode.RuntimeTests --extra-editor-arg=-batchmode --extra-editor-arg=-nographics - - | - {% if platform.name == "win" %}set{% endif %}{% if platform.name != "win" %}export{% endif %} UTR_VERSION=0.12.0 - {% if platform.name != "win" %}./{% endif %}utr --suite=playmode --platform={{ platform.standalone }} --player-load-path=build/players --artifacts_path=build/test-results --scripting-backend={{ backend }} --testfilter=Unity.Netcode.RuntimeTests --playergraphicsapi=Null - artifacts: - logs: - paths: - - "upm-ci~/test-results/**/*" - - "build/test-results/**" - dependencies: - - .yamato/project-pack.yml#pack_{{ project.name }} - -{% endfor -%} -{% endfor -%} -{% endfor -%} -{% endif -%} -{% endfor -%} \ No newline at end of file diff --git a/.yamato/webgl-build.yml b/.yamato/webgl-build.yml index 2a01a934b7..15db4b16ec 100644 --- a/.yamato/webgl-build.yml +++ b/.yamato/webgl-build.yml @@ -1,39 +1,53 @@ {% metadata_file .yamato/project.metafile %} --- -{% for project in projects -%} -{% if project.name == "testproject" -%} -{% for editor in project.test_editors -%} -build_{{ project.name }}_tests_{{ editor }}_webgl: - name: Build {{ project.name }} Tests - {{ editor }} - WebGL +# Builds a player on WebGl standalone platform without executing any tests. +# This setup performs build-only validation since WebGL runs in browser and for tests to be executed we would need to +# consider having a web server, browser automation and overall complex test setup. +# Default project (testptoject) in this case is used as a context. +# WebGL requires Il2cpp scripting backend so mono is not considered +# ARM64 architectures are not considered since Windows_arm64 is recommended to use only after builds and when it comes to macOS_arm64 there is problem with OpenCL not being available +# Builds are made on each desktop platform as in project.metafile declaration +{% for project in projects.default -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +webgl_build_{{ project.name }}_{{ platform }}_{{ editor }}: + name: WebGl Build - {{ project.name }} [{{ platform.name }}, {{ editor }}, il2cpp] agent: - type: Unity::VM - image: dots-ci/windows10:v1.493-auto - flavor: b1.xlarge + type: {% if platform.name == "mac" %} {{ platform.type }} {% else %} {{ platform.type }}::GPU {% endif %} + image: {{ platform.image }} + flavor: {{ platform.flavor }} commands: + # Installing tools - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat - - python .yamato/disable-burst-if-requested.py --project-path testproject --platform WebGL - - unity-downloader-cli -u {{ editor }} -c editor -c webgl -c il2cpp -w --fast + + # Platform specific UTR setup - | - set UTR_VERSION=0.12.0 - utr.bat --artifacts_path=artifacts --timeout=1800 --testproject={{ project.name }} --editor-location=.Editor --suite=playmode --platform=WebGL --build-only --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --scripting-backend=il2cpp --extra-editor-arg="-cloudEnvironment staging" +{% if platform.name == "win" %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat +{% else %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr +{% endif %} + + # Installing editor + - unity-downloader-cli -u {{ editor }} -c Editor -c webgl -c il2cpp -w --fast + + # Build Player + - | +{% if platform.name == "win" %} + utr.bat --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts_path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg="-cloudEnvironment staging" --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 +{% else %} + ./utr --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts-path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg="-cloudEnvironment staging" --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 +{% endif %} + artifacts: logs: paths: - - '*.log' - - '*.xml' - - artifacts/**/* - - testproject/Logs/** - - testproject/Library/*.log - - testproject/*.log - - testproject/Builds/*.log - - build/test-results/** - - artifacts/** - - build/players/** + - "artifacts/**/*" + - "build/players/**/*" variables: CI: true ENABLE_BURST_COMPILATION: False {% endfor -%} -{% endif -%} -{% endfor -%} \ No newline at end of file +{% endfor -%} +{% endfor -%} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index c4921d1fed..7e00fcbc8f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -5,6 +5,7 @@ using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using UnityEngine.TestTools; using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests @@ -114,6 +115,8 @@ public AnticipatedNetworkTransform GetOtherClientComponent() } [Test] + // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on Android + [UnityPlatform(exclude = new[] { RuntimePlatform.Android })] public void WhenAnticipating_ValueChangesImmediately() { var testComponent = GetTestComponent(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index a67be4de26..cb062a6a64 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -2159,6 +2159,7 @@ public string ArrayStr(NativeArray arr) where T : unmanaged } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on iOS [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeValueTypeNativeArrayNetworkVariables_ValuesAreSerializedCorrectly( @@ -2612,6 +2613,7 @@ public string DictionaryStr(Dictionary list) } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on iOS [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesAreSerializedCorrectly( @@ -2798,6 +2800,7 @@ public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesA } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on iOS [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_ValuesAreSerializedCorrectly( @@ -2953,6 +2956,7 @@ public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_Valu } [Test] + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is unstable on mobile devices [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeDictionaryNetworkVariables_ValuesAreSerializedCorrectly( diff --git a/com.unity.netcode.gameobjects/pvpExceptions.json b/com.unity.netcode.gameobjects/pvpExceptions.json new file mode 100644 index 0000000000..988e4d7a6d --- /dev/null +++ b/com.unity.netcode.gameobjects/pvpExceptions.json @@ -0,0 +1,1073 @@ +{ + "exempts": { + "PVP-41-1": { + "errors": [ + "CHANGELOG.md: line 9: Unreleased section is not allowed for public release" + ] + }, + "PVP-150-1": { + "errors": [ + "Unity.Netcode.Components.AnticipatedNetworkTransform: in list item context (only allowed in block or inline context)", + "Unity.Netcode.Components.AnticipatedNetworkTransform: in list item context (only allowed in block or inline context)", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateMove(Vector3): empty tag", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateRotate(Quaternion): empty tag", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateScale(Vector3): empty tag", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateState(TransformState): empty tag", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void Smooth(TransformState, TransformState, float): empty tag", + "Unity.Netcode.AnticipatedNetworkVariable: in list item context (only allowed in block or inline context)", + "Unity.Netcode.AnticipatedNetworkVariable: in list item context (only allowed in block or inline context)", + "Unity.Netcode.AnticipatedNetworkVariable: void Anticipate(T): empty tag", + "Unity.Netcode.AnticipatedNetworkVariable: void Smooth(in T, in T, float, AnticipatedNetworkVariable.SmoothDelegate): empty tag", + "Unity.Netcode.BufferedLinearInterpolatorVector3: XML is not well-formed: Missing closing quotation mark for string literal", + "Unity.Netcode.BufferSerializer: void SerializeValue(ref NativeArray, Allocator): unexpected ", + "Unity.Netcode.BufferSerializer: bool PreCheck(int): empty tag", + "Unity.Netcode.BufferSerializer: bool PreCheck(int): empty tag", + "Unity.Netcode.BytePacker: in block context; use instead", + "Unity.Netcode.ByteUnpacker: in block context; use instead", + "Unity.Netcode.ByteUnpacker: void ReadValuePacked(FastBufferReader, out string): empty tag", + "Unity.Netcode.FastBufferReader: in block context; use instead", + "Unity.Netcode.FastBufferReader: .ctor(NativeArray, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", + "Unity.Netcode.FastBufferReader: .ctor(NativeArray, Allocator, int, int, Allocator): empty tag", + "Unity.Netcode.FastBufferReader: .ctor(byte*, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", + "Unity.Netcode.FastBufferReader: .ctor(byte*, Allocator, int, int, Allocator): empty tag", + "Unity.Netcode.FastBufferReader: .ctor(FastBufferWriter, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", + "Unity.Netcode.FastBufferReader: .ctor(FastBufferWriter, Allocator, int, int, Allocator): empty tag", + "Unity.Netcode.FastBufferReader: .ctor(FastBufferReader, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", + "Unity.Netcode.FastBufferReader: .ctor(FastBufferReader, Allocator, int, int, Allocator): empty tag", + "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out T): empty tag", + "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out T): empty tag", + "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out T[]): empty tag", + "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out NativeArray, Allocator): empty tag", + "Unity.Netcode.FastBufferReader: void ReadNetworkSerializableInPlace(ref T): empty tag", + "Unity.Netcode.FastBufferReader: void ReadNetworkSerializableInPlace(ref T): empty tag", + "Unity.Netcode.FastBufferReader: void ReadPartialValue(out T, int, int): empty tag", + "Unity.Netcode.FastBufferReader: void ReadValueSafe(out NativeArray, Allocator): unexpected ", + "Unity.Netcode.FastBufferReader: void ReadValueSafeTemp(out NativeArray): unexpected ", + "Unity.Netcode.FastBufferWriter: in block context; use instead", + "Unity.Netcode.FastBufferWriter: bool TryBeginWriteInternal(int): empty tag", + "Unity.Netcode.FastBufferWriter: bool TryBeginWriteInternal(int): empty tag", + "Unity.Netcode.FastBufferWriter: bool TryBeginWriteInternal(int): empty tag", + "Unity.Netcode.FastBufferWriter: byte[] ToArray(): empty tag", + "Unity.Netcode.FastBufferWriter: byte* GetUnsafePtr(): empty tag", + "Unity.Netcode.FastBufferWriter: byte* GetUnsafePtrAtCurrentPosition(): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(string, bool): empty tag", + "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(in T): empty tag", + "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(T[], int, int): empty tag", + "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(T[], int, int): empty tag", + "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(NativeArray, int, int): empty tag", + "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(NativeArray, int, int): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(T[], int, int): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(T[], int, int): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(NativeArray, int, int): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(NativeArray, int, int): empty tag", + "Unity.Netcode.FastBufferWriter: void WritePartialValue(T, int, int): empty tag", + "Unity.Netcode.FastBufferWriter: void WritePartialValue(T, int, int): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T, ForStructs): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T, ForStructs): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T, ForStructs): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in NativeArray): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in NativeArray): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(in NativeArray): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(): empty tag", + "Unity.Netcode.FastBufferWriter: int GetWriteSize(): empty tag", + "Unity.Netcode.FastBufferWriter: void WriteValueSafe(in NativeArray): unexpected ", + "Unity.Netcode.ForceNetworkSerializeByMemcpy: empty tag", + "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: tag inside ", + "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: mixed block and inline content in ; wrap inline content in ", + "Unity.Netcode.Components.HalfVector3: .ctor(Vector3, bool3): unexpected ", + "Unity.Netcode.InvalidParentException: .ctor(string): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.InvalidParentException: .ctor(string): empty tag", + "Unity.Netcode.InvalidParentException: .ctor(string, Exception): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.InvalidParentException: .ctor(string, Exception): empty tag", + "Unity.Netcode.IReaderWriter: void SerializeValue(ref NativeArray, Allocator): unexpected ", + "Unity.Netcode.IReaderWriter: bool PreCheck(int): empty tag", + "Unity.Netcode.IReaderWriter: bool PreCheck(int): empty tag", + "Unity.Netcode.Editor.NetcodeEditorBase: void OnEnable(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void VerboseDebug(string): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void CreateServerAndClients(int): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: .ctor(HostOrServer): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void TimeTravel(double, int): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool CreateNewClients(int, out NetworkManager[], bool): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void StopOneClient(NetworkManager, bool): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void StartOneClient(NetworkManager): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Start(bool, NetworkManager, NetworkManager[], BeforeClientStartCallback, bool): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnected(NetworkManager, ResultWrapper, float): unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnected(NetworkManager[], ResultWrapper, float): unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnected(NetworkManager[], ResultWrapper, float): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnectedToServer(NetworkManager, ResultWrapper, float): unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(ulong, NetworkManager, ResultWrapper, bool, float): unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(Func, NetworkManager, ResultWrapper, bool, float): unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void GetNetworkObjectByRepresentationWithTimeTravel(Func, NetworkManager, ResultWrapper, bool, int): unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForCondition(Func, ResultWrapper, float, int): unexpected ", + "Unity.Netcode.NetworkBehaviour: NetworkObject: text or XML content outside a top-level tag", + "Unity.Netcode.NetworkBehaviour: void OnGainedOwnership(): mixed block and inline content in ; wrap inline content in ", + "Unity.Netcode.NetworkBehaviour: void OnLostOwnership(): mixed block and inline content in ; wrap inline content in ", + "Unity.Netcode.NetworkBehaviour: NetworkObject GetNetworkObject(ulong): empty tag", + "Unity.Netcode.NetworkBehaviour: NetworkObject GetNetworkObject(ulong): empty tag", + "Unity.Netcode.NetworkBehaviour: void OnSynchronize(ref BufferSerializer): unexpected ", + "Unity.Netcode.NetworkBehaviourReference: .ctor(NetworkBehaviour): empty tag", + "Unity.Netcode.NetworkConfig: string ToBase64(): empty tag", + "Unity.Netcode.NetworkConfig: ulong GetConfig(bool): empty tag", + "Unity.Netcode.NetworkConfig: ulong GetConfig(bool): empty tag", + "Unity.Netcode.NetworkConfig: bool CompareConfig(ulong): empty tag", + "Unity.Netcode.NetworkConfig: bool CompareConfig(ulong): empty tag", + "Unity.Netcode.NetworkList: .ctor(IEnumerable, NetworkVariableReadPermission, NetworkVariableWritePermission): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: .ctor(IEnumerable, NetworkVariableReadPermission, NetworkVariableWritePermission): empty tag", + "Unity.Netcode.NetworkList: IEnumerator GetEnumerator(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: void Add(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: void Clear(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: bool Contains(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: bool Remove(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: Count: cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: int IndexOf(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: void Insert(int, T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: void RemoveAt(int): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkList: this[int]: cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkManager: OnServerStopped: unexpected ", + "Unity.Netcode.NetworkManager: OnClientStopped: unexpected ", + "Unity.Netcode.NetworkManager: void AddNetworkPrefab(GameObject): empty tag", + "Unity.Netcode.NetworkManager: void AddNetworkPrefab(GameObject): empty tag", + "Unity.Netcode.NetworkManager: void RemoveNetworkPrefab(GameObject): empty tag", + "Unity.Netcode.NetworkManager: MaximumTransmissionUnitSize: empty tag", + "Unity.Netcode.NetworkManager: MaximumTransmissionUnitSize: unexpected ", + "Unity.Netcode.NetworkManager: void SetPeerMTU(ulong, int): empty tag", + "Unity.Netcode.NetworkManager: int GetPeerMTU(ulong): empty tag", + "Unity.Netcode.NetworkManager: int GetPeerMTU(ulong): empty tag", + "Unity.Netcode.NetworkManager: MaximumFragmentedMessageSize: empty tag", + "Unity.Netcode.NetworkManager: MaximumFragmentedMessageSize: unexpected ", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: Guid AddGameNetworkObject(string): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: T AddComponentToObject(Guid): empty tag", + "Unity.Netcode.NetworkObject: NetworkTransforms: must use self-closing syntax", + "Unity.Netcode.NetworkObject: bool TryRemoveParent(bool): empty tag", + "Unity.Netcode.NetworkObject.OnOwnershipPermissionsFailureDelegateHandler: empty tag", + "Unity.Netcode.NetworkObject.OnOwnershipRequestedDelegateHandler: empty tag", + "Unity.Netcode.NetworkObject.OnOwnershipRequestedDelegateHandler: empty tag", + "Unity.Netcode.NetworkObject.OnOwnershipRequestResponseDelegateHandler: empty tag", + "Unity.Netcode.NetworkObjectReference: .ctor(NetworkObject): empty tag", + "Unity.Netcode.NetworkObjectReference: .ctor(GameObject): empty tag", + "Unity.Netcode.INetworkPrefabInstanceHandler: NetworkObject Instantiate(ulong, Vector3, Quaternion): empty tag", + "Unity.Netcode.NetworkPrefabHandler: bool AddHandler(NetworkObject, INetworkPrefabInstanceHandler): empty tag", + "Unity.Netcode.NetworkPrefabHandler: bool AddHandler(uint, INetworkPrefabInstanceHandler): empty tag", + "Unity.Netcode.NetworkPrefabHandler: void AddNetworkPrefab(GameObject): empty tag", + "Unity.Netcode.NetworkPrefabHandler: void AddNetworkPrefab(GameObject): empty tag", + "Unity.Netcode.NetworkPrefabHandler: void RemoveNetworkPrefab(GameObject): empty tag", + "Unity.Netcode.NetworkPrefabsList: void Add(NetworkPrefab): empty tag", + "Unity.Netcode.NetworkPrefabsList: void Remove(NetworkPrefab): empty tag", + "Unity.Netcode.SceneEvent: in block context; use instead", + "Unity.Netcode.NetworkSceneManager: SceneEventProgressStatus UnloadScene(Scene): empty tag", + "Unity.Netcode.NetworkSceneManager.SceneEventDelegate: in block context; use instead", + "Unity.Netcode.NetworkSceneManager.SceneEventDelegate: empty tag", + "Unity.Netcode.NetworkSceneManager.OnLoadDelegateHandler: in block context; use instead", + "Unity.Netcode.NetworkSceneManager.OnUnloadDelegateHandler: in block context; use instead", + "Unity.Netcode.NetworkSceneManager.OnSynchronizeDelegateHandler: in block context; use instead", + "Unity.Netcode.NetworkSceneManager.OnEventCompletedDelegateHandler: in block context; use instead", + "Unity.Netcode.NetworkSceneManager.OnLoadCompleteDelegateHandler: in block context; use instead", + "Unity.Netcode.NetworkSceneManager.OnUnloadCompleteDelegateHandler: in block context; use instead", + "Unity.Netcode.NetworkSceneManager.OnSynchronizeCompleteDelegateHandler: in block context; use instead", + "Unity.Netcode.NetworkTime: NetworkTime TimeTicksAgo(int, float): empty tag", + "Unity.Netcode.NetworkTimeSystem: bool Advance(double): empty tag", + "Unity.Netcode.Components.NetworkTransform: void OnSynchronize(ref BufferSerializer): empty tag", + "Unity.Netcode.Components.NetworkTransform: void OnSynchronize(ref BufferSerializer): empty tag", + "Unity.Netcode.Components.NetworkTransform: void OnSynchronize(ref BufferSerializer): unexpected ", + "Unity.Netcode.Components.NetworkTransform: void OnInitialize(ref NetworkVariable): empty tag", + "Unity.Netcode.Components.NetworkTransform: void SetState(Vector3?, Quaternion?, Vector3?, bool): empty tag", + "Unity.Netcode.Components.NetworkTransform: void SetState(Vector3?, Quaternion?, Vector3?, bool): empty tag", + "Unity.Netcode.Components.NetworkTransform: void SetState(Vector3?, Quaternion?, Vector3?, bool): text or XML content outside a top-level tag", + "Unity.Netcode.Components.NetworkTransform: void Teleport(Vector3, Quaternion, Vector3): empty tag", + "Unity.Netcode.Components.NetworkTransform: void Teleport(Vector3, Quaternion, Vector3): empty tag", + "Unity.Netcode.Components.NetworkTransform: void Teleport(Vector3, Quaternion, Vector3): text or XML content outside a top-level tag", + "Unity.Netcode.Components.NetworkTransform: void OnUpdate(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", + "Unity.Netcode.NetworkTransport: in block context; use instead", + "Unity.Netcode.NetworkTransport: void Initialize(NetworkManager): suspicious '///' triple-slash inside XmlDoc comment", + "Unity.Netcode.NetworkTransport: void Initialize(NetworkManager): text or XML content outside a top-level tag", + "Unity.Netcode.NetworkTransport: void OnEarlyUpdate(): XML is not well-formed: End tag was not expected at this location", + "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): in block context (only allowed in top-level context)", + "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): empty tag", + "Unity.Netcode.NetworkVariableBase: void SetUpdateTraits(NetworkVariableUpdateTraits): empty tag", + "Unity.Netcode.NetworkVariableBase: bool ExceedsDirtinessThreshold(): empty tag", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: empty tag", + "Unity.Netcode.NetworkVariableSerialization: void Write(FastBufferWriter, ref T): empty tag", + "Unity.Netcode.NetworkVariableSerialization: void Read(FastBufferReader, ref T): empty tag", + "Unity.Netcode.NetworkVariableSerialization: void WriteDelta(FastBufferWriter, ref T, ref T): empty tag", + "Unity.Netcode.NetworkVariableSerialization: void ReadDelta(FastBufferReader, ref T): empty tag", + "Unity.Netcode.NetworkVariableSerialization: void Duplicate(in T, ref T): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Single(ulong, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Single(ulong, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeArray, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeArray, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeList, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeList, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(ulong[], RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(ulong[], RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(T, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(T, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeArray, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeArray, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeList, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeList, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong[], RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong[], RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(T, RpcTargetUse): empty tag", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(T, RpcTargetUse): empty tag", + "Unity.Netcode.SceneEventType: in block context; use instead", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: must use self-closing syntax", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedByMemcpy(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedByMemcpyArray(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_List(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_HashSet(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): unexpected ", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedINetworkSerializable(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedINetworkSerializableArray(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_ManagedINetworkSerializable(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_FixedString(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_FixedStringArray(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_ManagedIEquatable(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedIEquatable(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedIEquatableArray(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_List(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_HashSet(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): unexpected ", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedValueEquals(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedValueEqualsArray(): empty tag", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_ManagedClassEquals(): empty tag", + "Unity.Netcode.UserNetworkVariableSerialization: empty tag", + "Unity.Netcode.UserNetworkVariableSerialization.DuplicateValueDelegate: unexpected " + ] + }, + "PVP-151-1": { + "errors": [ + "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnUpdate(): undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkSpawn(): undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnDestroy(): undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnBeforeUpdateTransformState(): undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkTransformStateUpdated(ref NetworkTransformState, ref NetworkTransformState): undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnTransformUpdated(): undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Position: undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Rotation: undocumented", + "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Scale: undocumented", + "Unity.Netcode.StaleDataHandling: undocumented", + "Unity.Netcode.StaleDataHandling: Ignore: undocumented", + "Unity.Netcode.StaleDataHandling: Reanticipate: undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void OnInitialize(): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: bool ExceedsDirtinessThreshold(): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: .ctor(T, StaleDataHandling): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void Update(): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void Dispose(): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void Finalize(): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: bool IsDirty(): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void ResetDirty(): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void WriteDelta(FastBufferWriter): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void WriteField(FastBufferWriter): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void ReadField(FastBufferReader): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable: void ReadDelta(FastBufferReader, bool): undocumented", + "Unity.Netcode.AnticipatedNetworkVariable.OnAuthoritativeValueChangedDelegate: undocumented", + "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", + "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", + "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", + "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", + "Unity.Netcode.BaseRpcTarget: undocumented", + "Unity.Netcode.BaseRpcTarget: m_NetworkManager: undocumented", + "Unity.Netcode.BaseRpcTarget: void CheckLockBeforeDispose(): undocumented", + "Unity.Netcode.BaseRpcTarget: void Dispose(): undocumented", + "Unity.Netcode.BufferedLinearInterpolator: m_InterpStartValue: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: m_CurrentInterpValue: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: m_InterpEndValue: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: m_Buffer: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: void OnConvertTransformSpace(Transform, bool): undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: Item: undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: TimeSent: undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: .ctor(T, double): undocumented", + "Unity.Netcode.BufferedLinearInterpolatorQuaternion: void OnConvertTransformSpace(Transform, bool): undocumented", + "Unity.Netcode.BufferedLinearInterpolatorVector3: XML is not well-formed: Missing closing quotation mark for string literal", + "Unity.Netcode.BufferedLinearInterpolatorVector3: void OnConvertTransformSpace(Transform, bool): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: TimedOut: undocumented", + "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: bool OnHasConditionBeenReached(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: bool HasConditionBeenReached(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: void OnStarted(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: void Started(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: void OnFinished(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: void Finished(bool): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IConditionalPredicate: undocumented", + "Unity.Netcode.TestHelpers.Runtime.IConditionalPredicate: bool HasConditionBeenReached(): missing ", + "Unity.Netcode.TestHelpers.Runtime.IConditionalPredicate: void Finished(bool): missing ", + "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", + "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: NumberOfClients: undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: .ctor(HostOrServer, bool): undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: void OnServerAndClientsCreated(): undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: void OnNewClientCreated(NetworkManager): undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: IEnumerator HiddenObjectPromotedSessionOwnerNewClientSynchronizes(): missing ", + "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: .ctor(int): undocumented", + "Unity.Netcode.GenerateSerializationForTypeAttribute: .ctor(Type): undocumented", + "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.HalfVector3: .ctor(Vector3, bool3): missing ", + "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: string GetVector3Values(ref Vector3): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: string GetVector3Values(Vector3): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: float GetDeltaVarianceThreshold(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: float EulerDelta(float, float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: Vector3 EulerDelta(Vector3, Vector3): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: bool ApproximatelyEuler(float, float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: bool Approximately(float, float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: bool Approximately(Vector2, Vector2): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: bool Approximately(Vector3, Vector3): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: bool Approximately(Quaternion, Quaternion): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: bool ApproximatelyEuler(Vector3, Vector3): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: Vector3 GetRandomVector3(float, float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: .ctor(NetworkTopologyTypes, HostOrServer): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: .ctor(NetworkTopologyTypes): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: .ctor(HostOrServer): undocumented", + "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: .ctor(): undocumented", + "Unity.Netcode.InvalidParentException: .ctor(string): missing ", + "Unity.Netcode.InvalidParentException: .ctor(string, Exception): missing ", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: AllMessagesReceived: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: NumberOfMessagesReceived: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: string GetHooksStillWaiting(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: bool OnHasConditionBeenReached(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: void OnFinished(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: void Reset(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: .ctor(List): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ReceiptType: undocumented", + "Unity.Netcode.TestHelpers.Runtime.ReceiptType: Received: undocumented", + "Unity.Netcode.TestHelpers.Runtime.ReceiptType: Handled: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHookEntry: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHookEntry: m_NetworkManager: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHookEntry: void Initialize(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.MessageHookEntry: .ctor(NetworkManager, ReceiptType): undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: RealTimeSinceStartup: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: UnscaledTime: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: UnscaledDeltaTime: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: DeltaTime: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: FixedDeltaTime: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: StaticRealTimeSinceStartup: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: StaticUnscaledTime: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: StaticUnscaledDeltaTime: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: StaticDeltaTime: undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: void TimeTravel(double): undocumented", + "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: void Reset(): undocumented", + "Unity.Netcode.Editor.NetcodeEditorBase: missing ", + "Unity.Netcode.Editor.NetcodeEditorBase: void OnEnable(): missing ", + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: undocumented", + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: NetworkPrefabsPath: undocumented", + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: TempNetworkPrefabsPath: undocumented", + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: GenerateDefaultNetworkPrefabs: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: k_TickFrequency: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: s_GlobalTimeoutHelper: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: s_DefaultWaitForTick: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: NetcodeLogAssert: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void RegisterNetworkObject(NetworkObject): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void DeregisterNetworkObject(NetworkObject): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void DeregisterNetworkObject(ulong, ulong): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: TotalClients: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: k_DefaultTickRate: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: NumberOfClients: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_PlayerPrefab: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_ServerNetworkManager: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_ClientNetworkManagers: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_UseHost: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_DistributedAuthority: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_NetworkTopologyType: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool UseCMBService(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: NetworkTopologyTypes OnGetNetworkTopologyType(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void SetDistributedAuthorityProperties(NetworkManager): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_TargetFrameRate: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: m_EnableVerboseDebug: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool OnSetVerboseDebug(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: NetworkManagerInstatiationMode OnSetIntegrationTestMode(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnOneTimeSetup(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OneTimeSetup(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator OnSetup(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator SetUp(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientCreated(NetworkManager): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStarted(NetworkManager): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStartedAndConnected(NetworkManager): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator CreateAndStartNewClient(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void StopOneClientWithTimeTravel(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void StopOneClientWithTimeTravel(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void SetTimeTravelSimulatedLatency(float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void SetTimeTravelSimulatedDropRate(float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void SetTimeTravelSimulatedLatencyJitter(float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool CanStartServerAndClients(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator OnStartedServerAndClients(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator OnServerAndClientsConnected(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void ClientNetworkManagerPostStartInit(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: LogAllMessages: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldCheckForSpawnedPlayers(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StartServerAndClients(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool CanClientsLoad(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool CanClientsUnload(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool OnCanSceneCleanUpUnload(Scene): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator CoroutineShutdownAndCleanUp(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator OnTearDown(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnInlineTearDown(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator TearDown(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OneTimeTearDown(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool CanDestroyNetworkObject(NetworkObject): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForConditionOrTimeOut(Func, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForConditionOrTimeOut(Func, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForConditionOrTimeOut(Func, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForConditionOrTimeOutWithTimeTravel(Func, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForConditionOrTimeOutWithTimeTravel(Func, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForConditionOrTimeOutWithTimeTravel(Func, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForConditionOrTimeOut(IConditionalPredicate, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForConditionOrTimeOut(IConditionalPredicate, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForConditionOrTimeOut(IConditionalPredicate, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForConditionOrTimeOutWithTimeTravel(IConditionalPredicate, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForConditionOrTimeOutWithTimeTravel(IConditionalPredicate, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForConditionOrTimeOutWithTimeTravel(IConditionalPredicate, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForClientsConnectedOrTimeOut(NetworkManager[]): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForClientsConnectedOrTimeOutWithTimeTravel(NetworkManager[]): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForClientsConnectedOrTimeOut(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool WaitForClientsConnectedOrTimeOutWithTimeTravel(): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnPlayerObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnPlayerObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnPlayerObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: GameObject SpawnPlayerObject(GameObject, NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: List SpawnObjects(GameObject, NetworkManager, int, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: List SpawnObjects(GameObject, NetworkManager, int, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: List SpawnObjects(GameObject, NetworkManager, int, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: List SpawnObjects(GameObject, NetworkManager, int, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: List SpawnObjects(GameObject, NetworkManager, int, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: .ctor(NetworkTopologyTypes): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: .ctor(NetworkTopologyTypes, HostOrServer): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void AssertOnTimeout(string, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void AssertOnTimeout(string, TimeoutHelper): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForTicks(NetworkManager, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForTicks(NetworkManager, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator WaitForTicks(NetworkManager, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: uint GetTickRate(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: int GetFrameRate(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.SceneManagementState: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.SceneManagementState: SceneManagementEnabled: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.SceneManagementState: SceneManagementDisabled: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.NetworkManagerInstatiationMode: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.NetworkManagerInstatiationMode: PerTest: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.NetworkManagerInstatiationMode: AllTests: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.NetworkManagerInstatiationMode: DoNotCreate: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.HostOrServer: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.HostOrServer: Host: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.HostOrServer: Server: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest.HostOrServer: DAHost: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: DefaultMinFrames: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: DefaultTimeout: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: NetworkManagerInstances: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void RegisterHandlers(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void RegisterHandlers(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: NetworkManager CreateServer(bool): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Create(int, out NetworkManager, out NetworkManager[], int, bool, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Create(int, out NetworkManager, out NetworkManager[], int, bool, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool CreateNewClients(int, out NetworkManager[], bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool CreateNewClients(int, out NetworkManager[], bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void StopOneClient(NetworkManager, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Start(bool, NetworkManager, NetworkManager[], BeforeClientStartCallback, bool): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: uint GetNextGlobalIdHashValue(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IsNetcodeIntegrationTestRunning: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void RegisterNetcodeIntegrationTest(bool): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: GameObject CreateNetworkObjectPrefab(string, NetworkManager, params NetworkManager[]): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void MarkAsSceneObjectRoot(GameObject, NetworkManager, NetworkManager[]): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnected(NetworkManager, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnected(NetworkManager, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnected(NetworkManager[], ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnectedToServer(NetworkManager, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnectedToServer(NetworkManager, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(ulong, NetworkManager, ResultWrapper, bool, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(ulong, NetworkManager, ResultWrapper, bool, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(Func, NetworkManager, ResultWrapper, bool, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(Func, NetworkManager, ResultWrapper, bool, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void GetNetworkObjectByRepresentationWithTimeTravel(Func, NetworkManager, ResultWrapper, bool, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForCondition(Func, ResultWrapper, float, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForCondition(Func, ResultWrapper, float, int): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: uint GetGlobalObjectIdHash(NetworkObject): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers.MessageHandleCheck: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers.BeforeClientStartCallback: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers.ResultWrapper: missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers.ResultWrapper: Result: undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: .ctor(bool, bool): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void AddLog(string, string, LogType): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void OnTearDown(): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void Dispose(): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void LogWasNotReceived(LogType, string): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void LogWasNotReceived(LogType, Regex): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void LogWasReceived(LogType, string): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void LogWasReceived(LogType, Regex): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: bool HasLogBeenReceived(LogType, string): undocumented", + "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void Reset(): undocumented", + "Unity.Netcode.RpcException: undocumented", + "Unity.Netcode.RpcException: .ctor(string): undocumented", + "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkPostSpawn(): undocumented", + "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkSessionSynchronized(): undocumented", + "Unity.Netcode.NetworkBehaviour: void OnReanticipate(double): undocumented", + "Unity.Netcode.NetworkClient: NetworkTopologyType: undocumented", + "Unity.Netcode.NetworkClient: DAHost: undocumented", + "Unity.Netcode.NetworkConfig: Prefabs: undocumented", + "Unity.Netcode.NetworkConfig: NetworkTopology: undocumented", + "Unity.Netcode.NetworkConfig: UseCMBService: undocumented", + "Unity.Netcode.NetworkConfig: AutoSpawnPlayerPrefabClientSide: undocumented", + "Unity.Netcode.ConnectionEventData: EventType: undocumented", + "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetConvertedDelta(): missing ", + "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetDeltaPosition(): missing ", + "Unity.Netcode.NetworkList: .ctor(IEnumerable, NetworkVariableReadPermission, NetworkVariableWritePermission): missing ", + "Unity.Netcode.NetworkList: void Finalize(): undocumented", + "Unity.Netcode.NetworkList: IEnumerator GetEnumerator(): missing ", + "Unity.Netcode.NetworkList: IEnumerator GetEnumerator(): missing ", + "Unity.Netcode.NetworkList: void Add(T): missing ", + "Unity.Netcode.NetworkList: void Add(T): missing ", + "Unity.Netcode.NetworkList: void Clear(): missing ", + "Unity.Netcode.NetworkList: bool Contains(T): missing ", + "Unity.Netcode.NetworkList: bool Contains(T): missing ", + "Unity.Netcode.NetworkList: bool Contains(T): missing ", + "Unity.Netcode.NetworkList: bool Remove(T): missing ", + "Unity.Netcode.NetworkList: bool Remove(T): missing ", + "Unity.Netcode.NetworkList: bool Remove(T): missing ", + "Unity.Netcode.NetworkList: Count: missing ", + "Unity.Netcode.NetworkList: int IndexOf(T): missing ", + "Unity.Netcode.NetworkList: int IndexOf(T): missing ", + "Unity.Netcode.NetworkList: int IndexOf(T): missing ", + "Unity.Netcode.NetworkList: void Insert(int, T): missing ", + "Unity.Netcode.NetworkList: void Insert(int, T): missing ", + "Unity.Netcode.NetworkList: void Insert(int, T): missing ", + "Unity.Netcode.NetworkList: void RemoveAt(int): missing ", + "Unity.Netcode.NetworkList: void RemoveAt(int): missing ", + "Unity.Netcode.NetworkList: this[int]: missing ", + "Unity.Netcode.NetworkList: this[int]: missing ", + "Unity.Netcode.NetworkManager: CurrentSessionOwner: undocumented", + "Unity.Netcode.NetworkManager: void NetworkUpdate(NetworkUpdateStage): undocumented", + "Unity.Netcode.NetworkManager.ReanticipateDelegate: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: NetworkManagerObject: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: NetworkManagerGameObject: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: InstantiatedGameObjects: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: InstantiatedNetworkObjects: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: CurrentNetworkManagerMode: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: bool StartNetworkManager(out NetworkManager, NetworkManagerOperatingMode, NetworkConfig): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: void ShutdownNetworkManager(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: bool BuffersMatch(int, long, byte[], byte[]): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper.NetworkManagerOperatingMode: None: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper.NetworkManagerOperatingMode: Host: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper.NetworkManagerOperatingMode: Server: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper.NetworkManagerOperatingMode: Client: undocumented", + "Unity.Netcode.NetworkObject: void SetSceneObjectStatus(bool): undocumented", + "Unity.Netcode.NetworkObject: ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour): undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: None: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: Distributable: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: Transferable: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: RequestRequired: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: SessionOwner: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: Locked: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestRequired: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestInProgress: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: NotTransferrable: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: SessionOwnerOnly: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestSent: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: AlreadyOwner: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestRequiredNotSet: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: Locked: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestInProgress: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: SessionOwnerOnly: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Approved: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Locked: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: RequestInProgress: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: CannotRequest: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Denied: undocumented", + "Unity.Netcode.NetworkObject.OwnershipLockActions: None: undocumented", + "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndLock: undocumented", + "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndUnlock: undocumented", + "Unity.Netcode.NetworkObject.VisibilityDelegate: missing ", + "Unity.Netcode.NetworkObject.SpawnDelegate: missing ", + "Unity.Netcode.NetworkPrefab: bool Equals(NetworkPrefab): undocumented", + "Unity.Netcode.NetworkPrefab: SourcePrefabGlobalObjectIdHash: undocumented", + "Unity.Netcode.NetworkPrefab: TargetPrefabGlobalObjectIdHash: undocumented", + "Unity.Netcode.NetworkPrefab: bool Validate(int): undocumented", + "Unity.Netcode.NetworkPrefab: string ToString(): undocumented", + "Unity.Netcode.Editor.Configuration.NetworkPrefabProcessor: DefaultNetworkPrefabsPath: undocumented", + "Unity.Netcode.NetworkPrefabs: Prefabs: undocumented", + "Unity.Netcode.NetworkPrefabs: void Finalize(): undocumented", + "Unity.Netcode.NetworkPrefabs: void Initialize(bool): missing ", + "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", + "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", + "Unity.Netcode.NetworkPrefabs: void Remove(NetworkPrefab): missing ", + "Unity.Netcode.NetworkPrefabs: void Remove(GameObject): missing ", + "Unity.Netcode.Editor.NetworkPrefabsEditor: undocumented", + "Unity.Netcode.Editor.NetworkPrefabsEditor: void OnInspectorGUI(): undocumented", + "Unity.Netcode.NetworkSceneManager: List GetSceneMapping(MapTypes): undocumented", + "Unity.Netcode.NetworkSceneManager.MapTypes: undocumented", + "Unity.Netcode.NetworkSceneManager.MapTypes: ServerToClient: undocumented", + "Unity.Netcode.NetworkSceneManager.MapTypes: ClientToServer: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: MapType: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: Scene: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: ScenePresent: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: SceneName: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: ServerHandle: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: MappedLocalHandle: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: LocalHandle: undocumented", + "Unity.Netcode.NetworkSceneManager.SceneMap: void NetworkSerialize(BufferSerializer): undocumented", + "Unity.Netcode.NetworkSpawnManager: List GetConnectedPlayers(): undocumented", + "Unity.Netcode.NetworkSpawnManager: void InternalOnOwnershipChanged(ulong, ulong): undocumented", + "Unity.Netcode.NetworkTime: NetworkTime TimeTicksAgo(int, float): missing ", + "Unity.Netcode.Components.NetworkTransform: AuthorityMode: undocumented", + "Unity.Netcode.Components.NetworkTransform: PositionInLocalSpace: undocumented", + "Unity.Netcode.Components.NetworkTransform: RotationInLocalSpace: undocumented", + "Unity.Netcode.Components.NetworkTransform: void OnTransformUpdated(): undocumented", + "Unity.Netcode.Components.NetworkTransform: void OnBeforeUpdateTransformState(): undocumented", + "Unity.Netcode.Components.NetworkTransform: void OnOwnershipChanged(ulong, ulong): undocumented", + "Unity.Netcode.Components.NetworkTransform: void OnUpdate(): missing ", + "Unity.Netcode.Components.NetworkTransform: float GetTickLatency(): missing ", + "Unity.Netcode.Components.NetworkTransform: float GetTickLatencyInSeconds(): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsUnreliableFrameSync(): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsReliableStateUpdate(): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkTransform.AuthorityModes: undocumented", + "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Server: undocumented", + "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Owner: undocumented", + "Unity.Netcode.NetworkTransport: void OnEarlyUpdate(): XML is not well-formed: End tag was not expected at this location", + "Unity.Netcode.NetworkTransport: NetworkTopologyTypes OnCurrentTopology(): undocumented", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.NetworkTopologyTypes: undocumented", + "Unity.Netcode.NetworkTopologyTypes: ClientServer: undocumented", + "Unity.Netcode.NetworkTopologyTypes: DistributedAuthority: undocumented", + "Unity.Netcode.NetworkVariable: CheckExceedsDirtinessThreshold: undocumented", + "Unity.Netcode.NetworkVariable: bool ExceedsDirtinessThreshold(): undocumented", + "Unity.Netcode.NetworkVariable: void OnInitialize(): undocumented", + "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): missing ", + "Unity.Netcode.NetworkVariable: void Dispose(): undocumented", + "Unity.Netcode.NetworkVariable: void Finalize(): undocumented", + "Unity.Netcode.NetworkVariable.CheckExceedsDirtinessThresholdDelegate: undocumented", + "Unity.Netcode.NetworkVariableUpdateTraits: undocumented", + "Unity.Netcode.NetworkVariableUpdateTraits: MinSecondsBetweenUpdates: undocumented", + "Unity.Netcode.NetworkVariableUpdateTraits: MaxSecondsBetweenUpdates: undocumented", + "Unity.Netcode.NetworkVariableBase: NetworkBehaviour GetBehaviour(): undocumented", + "Unity.Netcode.NetworkVariableBase: void MarkNetworkBehaviourDirty(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: NumberOfClients: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: .ctor(HostOrServer): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: bool OnSetVerboseDebug(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: IEnumerator OnSetup(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: void OnCreatePlayerPrefab(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: IEnumerator TestListBuiltInTypeCollections(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: IEnumerator TestListSerializableObjectCollections(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: IEnumerator TestDictionaryCollections(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: IEnumerator TestDictionaryNestedCollections(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: IEnumerator TestHashSetBuiltInTypeCollections(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests: NumberOfClients: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests: .ctor(HostOrServer, CollectionTypes): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests: void OnServerAndClientsCreated(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests: IEnumerator CollectionAndOwnershipChangingTest(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests.CollectionTypes: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests.CollectionTypes: Dictionary: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsChangingTests.CollectionTypes: List: undocumented", + "Unity.Netcode.RuntimeTests.DictionaryCollectionUpdateHelper: bool OnValidateAgainst(BaseCollectionUpdateHelper): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryCollectionUpdateHelper: void OnClear(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryCollectionUpdateHelper: void AddItem(): undocumented", + "Unity.Netcode.RuntimeTests.ListCollectionUpdateHelper: bool OnValidateAgainst(BaseCollectionUpdateHelper): undocumented", + "Unity.Netcode.RuntimeTests.ListCollectionUpdateHelper: void OnClear(): undocumented", + "Unity.Netcode.RuntimeTests.ListCollectionUpdateHelper: void AddItem(): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: VerboseMode: undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: HelperState: undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void SetState(HelperStates): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: bool OnValidateAgainst(BaseCollectionUpdateHelper): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: bool ValidateAgainst(BaseCollectionUpdateHelper): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void OnNetworkSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void OnClear(): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void Clear(): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void AddItem(): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void OnOwnershipChanged(ulong, ulong): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper: void Log(string): undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper.HelperStates: undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper.HelperStates: Stop: undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper.HelperStates: Start: undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper.HelperStates: Pause: undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper.HelperStates: ClearToChangeOwner: undocumented", + "Unity.Netcode.RuntimeTests.BaseCollectionUpdateHelper.HelperStates: ChangingOwner: undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: Instances: undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void ResetState(): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: ListCollectionServer: undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: ListCollectionOwner: undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: NetworkVariableChanges: undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: HashSet GetHashSetValues(int): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: NetworkVariable> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: HashSet OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: HashSet OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void Add(int, Targets): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void AddRange(HashSet, Targets): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void Remove(int, Targets): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void FullSet(HashSet, Targets): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void TrackChanges(Targets, HashSet, HashSet): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void OnServerListValuesChanged(HashSet, HashSet): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void OnOwnerListValuesChanged(HashSet, HashSet): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.HashSetBaseTypeTestHelper: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: Instances: undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void ResetState(): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: ListCollectionServer: undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: ListCollectionOwner: undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: NetworkVariableChanges: undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: Dictionary GetDictionaryValues(int): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: Dictionary> GetNestedDictionaryValues(int): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: NetworkVariable>> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: Dictionary> OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: Dictionary> OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: bool UpdateValue((int, Dictionary), Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void Add((int, Dictionary), Targets): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void Remove(int, Targets): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void FullSet(Dictionary>, Targets): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void TrackChanges(Targets, Dictionary>, Dictionary>): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void OnServerListValuesChanged(Dictionary>, Dictionary>): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void OnOwnerListValuesChanged(Dictionary>, Dictionary>): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.NestedDictionaryTestHelper: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: Instances: undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void ResetState(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: ListCollectionServer: undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: ListCollectionOwner: undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: NetworkVariableChanges: undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: Dictionary GetDictionaryValues(int): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: NetworkVariable> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: Dictionary OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: Dictionary OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: bool UpdateValue((int, SerializableObject), Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void Add((int, SerializableObject), Targets): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void Remove(int, Targets): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void FullSet(Dictionary, Targets): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void TrackChanges(Targets, Dictionary, Dictionary): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void OnServerListValuesChanged(Dictionary, Dictionary): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void OnOwnerListValuesChanged(Dictionary, Dictionary): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void InitValues(): undocumented", + "Unity.Netcode.RuntimeTests.DictionaryTestHelper: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: SerializableObject GetRandomObject(): undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: List GetListOfRandomObjects(int): undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: List> GetListOfListOfRandomObjects(int, int): undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: IntValue: undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: LongValue: undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: FloatValue: undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: string ToString(): undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: void NetworkSerialize(BufferSerializer): undocumented", + "Unity.Netcode.RuntimeTests.SerializableObject: bool Equals(SerializableObject): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: Instances: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void ResetState(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: ListCollectionServer: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: ListCollectionOwner: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: NetworkVariableChanges: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: NetworkVariable>> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: List> OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: List> OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void UpdateValue(List, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void Add(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void AddRange(List>, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void Insert(List, int, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void Remove(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void FullSet(List>, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void TrackChanges(Targets, List>, List>): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void OnServerListValuesChanged(List>, List>): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void OnOwnerListValuesChanged(List>, List>): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListSerializableObject: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: Instances: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void ResetState(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: ListCollectionServer: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: ListCollectionOwner: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: NetworkVariableChanges: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: NetworkVariable> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: List OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: List OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void UpdateValue(SerializableObject, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void Add(SerializableObject, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void AddRange(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void Insert(SerializableObject, int, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void Remove(SerializableObject, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void FullSet(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void TrackChanges(Targets, List, List): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void OnServerListValuesChanged(List, List): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void OnOwnerListValuesChanged(List, List): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperSerializableObject: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: Instances: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void ResetState(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: ListCollectionServer: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: ListCollectionOwner: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: NetworkVariableChanges: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: NetworkVariable>> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: List> OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: List> OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void UpdateValue(List, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void Add(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void AddRange(List>, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void Insert(List, int, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void Remove(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void FullSet(List>, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void TrackChanges(Targets, List>, List>): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void OnServerListValuesChanged(List>, List>): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void OnOwnerListValuesChanged(List>, List>): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperListInt: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: Instances: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void ResetState(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: ListCollectionServer: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: ListCollectionOwner: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: NetworkVariableChanges: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: NetworkVariable> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: List OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: List OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void UpdateValue(int, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void Add(int, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void AddRange(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void Insert(int, int, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void Remove(int, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void FullSet(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void TrackChanges(Targets, List, List): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void OnServerListValuesChanged(List, List): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void OnOwnerListValuesChanged(List, List): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperInt: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase: IsDebugMode: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase: string GetLog(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase: void LogMessage(string): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase: void LogStart(): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase: void SetDebugMode(bool): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase: bool CompareTrackedChanges(Targets): undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.Targets: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.Targets: Server: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.Targets: Owner: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.DeltaTypes: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.DeltaTypes: Added: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.DeltaTypes: Removed: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.DeltaTypes: Changed: undocumented", + "Unity.Netcode.RuntimeTests.ListTestHelperBase.DeltaTypes: UnChanged: undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: NetworkVariable> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: List OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: List OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void UpdateValue(T, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void Add(T, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void AddRange(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void Insert(T, int, Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void Remove(T, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void FullSet(List, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void TrackChanges(Targets, List, List): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void OnServerListValuesChanged(List, List): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void OnOwnerListValuesChanged(List, List): undocumented", + "Unity.Netcode.RuntimeTests.IListTestHelperBase: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: NetworkVariable> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: Dictionary OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: Dictionary OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: bool UpdateValue((TKey, TValue), Targets, bool): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void Add((TKey, TValue), Targets): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void Remove(TKey, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void FullSet(Dictionary, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void TrackChanges(Targets, Dictionary, Dictionary): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void OnServerListValuesChanged(Dictionary, Dictionary): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void OnOwnerListValuesChanged(Dictionary, Dictionary): undocumented", + "Unity.Netcode.RuntimeTests.IDictionaryTestHelperBase: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: bool ValidateInstances(): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: NetworkVariable> GetNetVar(Targets): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: HashSet OnSetServerValues(): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: HashSet OnSetOwnerValues(): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void Add(T, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void Remove(T, Targets): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void Clear(Targets): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void TrackChanges(Targets, HashSet, HashSet): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void OnServerListValuesChanged(HashSet, HashSet): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void OnOwnerListValuesChanged(HashSet, HashSet): undocumented", + "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void ResetTrackedChanges(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: OnValueChanged: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: .ctor(NetworkVariableBase): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper.OnMyValueChangedDelegateHandler: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableBaseHelper: .ctor(NetworkVariableBase): undocumented", + "Unity.Netcode.NetworkVariableSerialization: void WriteDelta(FastBufferWriter, ref T, ref T): missing ", + "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", + "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", + "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", + "Unity.Netcode.RuntimeTests.ByteEnum: undocumented", + "Unity.Netcode.RuntimeTests.ByteEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.ByteEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.ByteEnum: C: undocumented", + "Unity.Netcode.RuntimeTests.SByteEnum: undocumented", + "Unity.Netcode.RuntimeTests.SByteEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.SByteEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.SByteEnum: C: undocumented", + "Unity.Netcode.RuntimeTests.ShortEnum: undocumented", + "Unity.Netcode.RuntimeTests.ShortEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.ShortEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.ShortEnum: C: undocumented", + "Unity.Netcode.RuntimeTests.UShortEnum: undocumented", + "Unity.Netcode.RuntimeTests.UShortEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.UShortEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.UShortEnum: C: undocumented", + "Unity.Netcode.RuntimeTests.IntEnum: undocumented", + "Unity.Netcode.RuntimeTests.IntEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.IntEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.IntEnum: C: undocumented", + "Unity.Netcode.RuntimeTests.UIntEnum: undocumented", + "Unity.Netcode.RuntimeTests.UIntEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.UIntEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.UIntEnum: C: undocumented", + "Unity.Netcode.RuntimeTests.LongEnum: undocumented", + "Unity.Netcode.RuntimeTests.LongEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.LongEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.LongEnum: C: undocumented", + "Unity.Netcode.RuntimeTests.ULongEnum: undocumented", + "Unity.Netcode.RuntimeTests.ULongEnum: A: undocumented", + "Unity.Netcode.RuntimeTests.ULongEnum: B: undocumented", + "Unity.Netcode.RuntimeTests.ULongEnum: C: undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnNetworkSpawn(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void RegisterAndLabelNetworkObject(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void DeRegisterNetworkObject(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnLostOwnership(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnGainedOwnership(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnDestroy(): undocumented", + "Unity.Netcode.RpcAttribute: RequireOwnership: undocumented", + "Unity.Netcode.RpcAttribute: DeferLocal: undocumented", + "Unity.Netcode.RpcAttribute: AllowTargetOverride: undocumented", + "Unity.Netcode.RpcAttribute: .ctor(SendTo): undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: Delivery: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: RequireOwnership: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: DeferLocal: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: AllowTargetOverride: undocumented", + "Unity.Netcode.ServerRpcAttribute: RequireOwnership: undocumented", + "Unity.Netcode.ServerRpcAttribute: .ctor(): undocumented", + "Unity.Netcode.ClientRpcAttribute: .ctor(): undocumented", + "Unity.Netcode.LocalDeferMode: undocumented", + "Unity.Netcode.LocalDeferMode: Default: undocumented", + "Unity.Netcode.LocalDeferMode: Defer: undocumented", + "Unity.Netcode.LocalDeferMode: SendImmediate: undocumented", + "Unity.Netcode.RpcSendParams: Target: undocumented", + "Unity.Netcode.RpcSendParams: LocalDeferMode: undocumented", + "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(BaseRpcTarget): undocumented", + "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(LocalDeferMode): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcSendParams): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(BaseRpcTarget): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(LocalDeferMode): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcReceiveParams): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: NumberOfClients: undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: .ctor(HostOrServer): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: IEnumerator OnSetup(): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: void OnCreatePlayerPrefab(): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: IEnumerator ProxyDoesNotInvokeOnSender(): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: ReceivedRpc: undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: void SendToEveryOneButMe(): undocumented", + "Unity.Netcode.RpcTarget: void Dispose(): undocumented", + "Unity.Netcode.RpcTarget: BaseRpcTarget Group(T, RpcTargetUse): missing ", + "Unity.Netcode.RpcTarget: BaseRpcTarget Not(T, RpcTargetUse): missing ", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: OnServerRpcAction: undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: OnClientRpcAction: undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: void MyServerRpc(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: void MyClientRpc(ClientRpcParams): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: k_DefaultTimeOutWaitPeriod: undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: m_IsStarted: undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: TimedOut: undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: float GetTimeElapsed(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void OnStart(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void Start(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void OnStop(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void Stop(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: bool OnHasTimedOut(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: bool HasTimedOut(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: .ctor(float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: int GetFrameCount(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: void OnStop(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: bool OnHasTimedOut(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: void OnStart(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: .ctor(float, uint): undocumented", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): missing ", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): missing ", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): missing ", + "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): missing ", + "Unity.Netcode.Transports.UTP.UnityTransport: m_Driver: undocumented", + "Unity.Netcode.Transports.UTP.UnityTransport: NetworkConnection Connect(NetworkEndPoint): undocumented", + "Unity.Netcode.Transports.UTP.UnityTransport: NetworkTopologyTypes OnCurrentTopology(): undocumented", + "Unity.Netcode.Transports.UTP.UnityTransport.ConnectionAddressData: IsIpv6: undocumented", + "Unity.Netcode.UserNetworkVariableSerialization.WriteDeltaDelegate: missing ", + "Unity.Netcode.UserNetworkVariableSerialization.DuplicateValueDelegate: missing " + ] + } + }, + "extends": [ + "rme", + "supported" + ] +} diff --git a/com.unity.netcode.gameobjects/pvpExceptions.json.meta b/com.unity.netcode.gameobjects/pvpExceptions.json.meta new file mode 100644 index 0000000000..897531988f --- /dev/null +++ b/com.unity.netcode.gameobjects/pvpExceptions.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3460732dd46332e48acdf417ee62dbd4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 4ff5b8e497a0e3faeba380d13291c24bc5e120dd Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 11 Feb 2025 18:58:12 -0600 Subject: [PATCH 171/236] fix: nested NetworkTransforms with different authority settings (#3209) * fix First pass fixes for mixed authority mode nested networktransforms. * style adding additional comments * style Remove commented code. Add additional comments for clarity. * update adding changelog entry. * test Adding integration test to validate that mixed authority NetworkTransform hierarchies are working properly. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Components/NetworkTransform.cs | 63 ++++++++--- .../NetworkTransformMixedAuthorityTests.cs | 103 ++++++++++++++++++ ...etworkTransformMixedAuthorityTests.cs.meta | 2 + 4 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f63cda27e3..30c1d4c860 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) - Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219) +- Fixed issue where a `NetworkObject` with nested `NetworkTransform` components of varying authority modes was not being taken into consideration and would break both the initial `NetworkTransform` synchronization and fail to properly handle synchronized state updates of the nested `NetworkTransform` components. (#3209) - Fixed issue with distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, were not being distributed to the same client upon the owning client disconnecting when using a distributed authority network topology. (#3203) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) - Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 3d2be47c63..7dd677b135 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1679,8 +1679,8 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz // Synchronize any nested NetworkTransforms with the parent's foreach (var childNetworkTransform in NetworkObject.NetworkTransforms) { - // Don't update the same instance - if (childNetworkTransform == this) + // Don't update the same instance or any nested NetworkTransform with a different authority mode + if (childNetworkTransform == this || childNetworkTransform.AuthorityMode != AuthorityMode) { continue; } @@ -2908,8 +2908,8 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf // Synchronize any nested NetworkTransforms with the parent's foreach (var childNetworkTransform in NetworkObject.NetworkTransforms) { - // Don't update the same instance - if (childNetworkTransform == this) + // Don't update the same instance or any nested NetworkTransform with a different authority mode + if (childNetworkTransform == this || childNetworkTransform.AuthorityMode != AuthorityMode) { continue; } @@ -3032,15 +3032,29 @@ private void NonAuthorityFinalizeSynchronization() // For all child NetworkTransforms nested under the same NetworkObject, // we apply the initial synchronization based on their parented/ordered // heirarchy. - if (SynchronizeState.IsSynchronizing && m_IsFirstNetworkTransform) + if (SynchronizeState.IsSynchronizing) { - foreach (var child in NetworkObject.NetworkTransforms) + if (m_IsFirstNetworkTransform) { - child.ApplySynchronization(); + foreach (var child in NetworkObject.NetworkTransforms) + { + // Don't initialize any nested NetworkTransforms that this instance has authority over + if (child.CanCommitToTransform) + { + continue; + } + child.ApplySynchronization(); - // For all nested (under the root/same NetworkObject) child NetworkTransforms, we need to run through - // initialization once more to assure any values applied or stored are relative to the Root's transform. - child.InternalInitialization(); + // For all like-authority nested (under the root/same NetworkObject) child NetworkTransforms, we need to run through + // initialization once more to assure any values applied or stored are relative to the Root's transform. + child.InternalInitialization(); + } + } + else // Otherwise, just run through standard synchronization of this instance + if (!CanCommitToTransform) + { + ApplySynchronization(); + InternalInitialization(); } } } @@ -3075,25 +3089,40 @@ protected internal override void InternalOnNetworkPostSpawn() // This is a special case for client-server where a server is spawning an owner authoritative NetworkObject but has yet to serialize anything. // When the server detects that: // - We are not in a distributed authority session (DAHost check). - // - This is the first/root NetworkTransform. // - We are in owner authoritative mode. // - The NetworkObject is not owned by the server. // - The SynchronizeState.IsSynchronizing is set to false. // Then we want to: // - Force the "IsSynchronizing" flag so the NetworkTransform has its state updated properly and runs through the initialization again. // - Make sure the SynchronizingState is updated to the instantiated prefab's default flags/settings. - if (NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode && m_IsFirstNetworkTransform && !OnIsServerAuthoritative() && !IsOwner && !SynchronizeState.IsSynchronizing) + if (NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode && !IsOwner && !OnIsServerAuthoritative() && !SynchronizeState.IsSynchronizing) { - // Assure the first/root NetworkTransform has the synchronizing flag set so the server runs through the final post initialization steps - SynchronizeState.IsSynchronizing = true; - // Assure the SynchronizeState matches the initial prefab's values for each associated NetworkTransfrom (this includes root + all children) - foreach (var child in NetworkObject.NetworkTransforms) + // Handle the first/root NetworkTransform slightly differently to have a sequenced synchronization of like authority nested NetworkTransform components + if (m_IsFirstNetworkTransform) { - child.ApplyPlayerTransformState(); + // Assure the NetworkTransform has the synchronizing flag set so the server runs through the final post initialization steps + SynchronizeState.IsSynchronizing = true; + + // Assure the SynchronizeState matches the initial prefab's values for each associated NetworkTransfrom (this includes root + all children) + foreach (var child in NetworkObject.NetworkTransforms) + { + // Don't ApplyPlayerTransformState to any nested NetworkTransform with a different authority mode + if (child != this && child.AuthorityMode != AuthorityMode) + { + continue; + } + child.ApplyPlayerTransformState(); + } } + else + { + ApplyPlayerTransformState(); + } + // Now fall through to the final synchronization portion of the spawning for NetworkTransform } + // Standard non-authority synchronization is handled here if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) { NonAuthorityFinalizeSynchronization(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs new file mode 100644 index 0000000000..663ba49810 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs @@ -0,0 +1,103 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + internal class NetworkTransformMixedAuthorityTests : IntegrationTestWithApproximation + { + private const float k_MotionMagnitude = 5.5f; + private const int k_Iterations = 4; + + protected override int NumberOfClients => 2; + + private StringBuilder m_ErrorMsg = new StringBuilder(); + private List m_NetworkManagers = new List(); + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + + var childGameObject = new GameObject(); + childGameObject.transform.parent = m_PlayerPrefab.transform; + var childNetworkTransform = childGameObject.AddComponent(); + childNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + childNetworkTransform.InLocalSpace = true; + + base.OnCreatePlayerPrefab(); + } + + private void MovePlayers() + { + foreach (var networkManager in m_NetworkManagers) + { + var direction = GetRandomVector3(-1.0f, 1.0f); + var playerObject = networkManager.LocalClient.PlayerObject; + var playerObjectId = networkManager.LocalClient.PlayerObject.NetworkObjectId; + // Server authoritative + var serverPlayerClone = m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerObjectId]; + serverPlayerClone.transform.position += direction * k_MotionMagnitude; + // Owner authoritative + var childTransform = networkManager.LocalClient.PlayerObject.transform.GetChild(0); + childTransform.localPosition += direction * k_MotionMagnitude; + } + } + + private bool AllInstancePositionsMatch() + { + m_ErrorMsg.Clear(); + foreach (var networkManager in m_NetworkManagers) + { + var playerObject = networkManager.LocalClient.PlayerObject; + var playerObjectId = networkManager.LocalClient.PlayerObject.NetworkObjectId; + var serverRootPosition = m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerObjectId].transform.position; + var ownerChildPosition = networkManager.LocalClient.PlayerObject.transform.GetChild(0).localPosition; + foreach (var client in m_NetworkManagers) + { + if (client == networkManager) + { + continue; + } + var playerClone = client.SpawnManager.SpawnedObjects[playerObjectId]; + var cloneRootPosition = playerClone.transform.position; + var cloneChildPosition = playerClone.transform.GetChild(0).localPosition; + + if (!Approximately(serverRootPosition, cloneRootPosition)) + { + m_ErrorMsg.AppendLine($"[{playerObject.name}][{playerClone.name}] Root mismatch ({GetVector3Values(serverRootPosition)})({GetVector3Values(cloneRootPosition)})!"); + } + + if (!Approximately(ownerChildPosition, cloneChildPosition)) + { + m_ErrorMsg.AppendLine($"[{playerObject.name}][{playerClone.name}] Child mismatch ({GetVector3Values(ownerChildPosition)})({GetVector3Values(cloneChildPosition)})!"); + } + } + } + return m_ErrorMsg.Length == 0; + } + + /// + /// Client-Server Only + /// Validates that mixed authority is working properly + /// Root -- Server Authoritative + /// |--Child -- Owner Authoritative + /// + [UnityTest] + public IEnumerator MixedAuthorityTest() + { + m_NetworkManagers.Add(m_ServerNetworkManager); + m_NetworkManagers.AddRange(m_ClientNetworkManagers); + + for (int i = 0; i < k_Iterations; i++) + { + MovePlayers(); + yield return WaitForConditionOrTimeOut(AllInstancePositionsMatch); + AssertOnTimeout($"Transforms failed to synchronize!"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs.meta new file mode 100644 index 0000000000..33fe880cfa --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1bb4423be663c944ab55615995e28612 \ No newline at end of file From 6c46dfd6e5041777f2c84feccbc37e00f7552c0d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 11 Feb 2025 20:39:04 -0600 Subject: [PATCH 172/236] fix: network objects to synchronize scene changes edge cases (#3230) * fix Clean up scene migration synchronization table entry when GameObject is destroyed when it should not have been. * fix Clean up scene migration synchronization table when NetworkManager shuts down. * fix adding check for any in-scene placed NetworkObjects. removing whenever destroyed (if entry still exists). * test Minor test to validate the NetworkObject is no longer within the NetworkObject.NetworkObjectsToSynchronizeSceneChanges list upon destroying it prior to it being despawned. * update Adding change log entries. * fix Removing static NetworkObjectsToSynchronizeSceneChanges and CleanUpDisposedObjects and migrating them to normal properties within the NetworkSpawnManager. * test Adding a validation at the end of the scene load-unload test to assure the NetworkObjectsToSynchronizeSceneChanges has no remaining in-scene placed NetworkObjects when all scenes are unloaded. Removing a verbose setting from InScenePlacedNetworkObjectTests. * fix Fixing issue with logic for scene migration synchronization. * update switching from a list to a stack * test Had to adjust for the changes from static to NetworkManager relative (for DA mode). --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Core/NetworkManager.cs | 2 +- .../Runtime/Core/NetworkObject.cs | 64 ++++++----------- .../Runtime/Spawning/NetworkSpawnManager.cs | 68 ++++++++++++++++--- .../NetworkObjectDestroyTests.cs | 15 ++++ .../InScenePlacedNetworkObjectTests.cs | 2 +- .../NetworkSceneManagerEventDataPoolTest.cs | 24 ++++++- 7 files changed, 120 insertions(+), 57 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 30c1d4c860..17abe14225 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) +- Fixed issue where the scene migration synchronization table was not cleaned up if the `GameObject` of a `NetworkObject` is destroyed before it should have been. (#3230) +- Fixed issue where the scene migration synchronization table was not cleaned up upon `NetworkManager` shutting down. (#3230) - Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219) - Fixed issue where a `NetworkObject` with nested `NetworkTransform` components of varying authority modes was not being taken into consideration and would break both the initial `NetworkTransform` synchronization and fail to properly handle synchronized state updates of the nested `NetworkTransform` components. (#3209) - Fixed issue with distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, were not being distributed to the same client upon the owning client disconnecting when using a distributed authority network topology. (#3203) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 5ae8e1d91e..54e5b648b3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -401,7 +401,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) } // Update any NetworkObject's registered to notify of scene migration changes. - NetworkObject.UpdateNetworkObjectSceneChanges(); + SpawnManager.UpdateNetworkObjectSceneChanges(); // This should be invoked just prior to the MessageManager processes its outbound queue. SceneManager.CheckForAndSendNetworkObjectSceneChanged(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index cf8f1b2bb5..332b1304ec 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -319,6 +319,9 @@ private void CheckForInScenePlaced() EditorUtility.SetDirty(this); } IsSceneObject = true; + + // Default scene migration synchronization to false for in-scene placed NetworkObjects + SceneMigrationSynchronization = false; } } #endif // UNITY_EDITOR @@ -1596,6 +1599,9 @@ private void OnDestroy() if (NetworkManager.IsListening && !isAuthority && IsSpawned && (IsSceneObject == null || (IsSceneObject.Value != true))) { + // If we destroyed a GameObject with a NetworkObject component on the non-authority side, handle cleaning up the SceneMigrationSynchronization. + NetworkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); + // Clients should not despawn NetworkObjects while connected to a session, but we don't want to destroy the current call stack // if this happens. Instead, we should just generate a network log error and exit early (as long as we are not shutting down). if (!NetworkManager.ShutdownInProgress) @@ -1617,6 +1623,9 @@ private void OnDestroy() // Otherwise, clients can despawn NetworkObjects while shutting down and should not generate any messages when this happens } + // Always attempt to remove from scene changed updates + NetworkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); + if (NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { if (this == networkObject) @@ -2392,11 +2401,6 @@ internal void InvokeBehaviourNetworkSpawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); - if (SceneMigrationSynchronization && NetworkManager.NetworkConfig.EnableSceneManagement) - { - AddNetworkObjectToSceneChangedUpdates(this); - } - for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) @@ -2451,21 +2455,15 @@ internal void InternalInSceneNetworkObjectsSpawned() } } - - internal void InvokeBehaviourNetworkDespawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); + NetworkManager.SpawnManager.RemoveNetworkObjectFromSceneChangedUpdates(this); for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { ChildNetworkBehaviours[i].InternalOnNetworkDespawn(); } - - if (SceneMigrationSynchronization && NetworkManager.NetworkConfig.EnableSceneManagement) - { - RemoveNetworkObjectFromSceneChangedUpdates(this); - } } private List m_ChildNetworkBehaviours; @@ -3276,31 +3274,6 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } - internal static Dictionary NetworkObjectsToSynchronizeSceneChanges = new Dictionary(); - - internal static void AddNetworkObjectToSceneChangedUpdates(NetworkObject networkObject) - { - if (!NetworkObjectsToSynchronizeSceneChanges.ContainsKey(networkObject.NetworkObjectId)) - { - NetworkObjectsToSynchronizeSceneChanges.Add(networkObject.NetworkObjectId, networkObject); - } - - networkObject.UpdateForSceneChanges(); - } - - internal static void RemoveNetworkObjectFromSceneChangedUpdates(NetworkObject networkObject) - { - NetworkObjectsToSynchronizeSceneChanges.Remove(networkObject.NetworkObjectId); - } - - internal static void UpdateNetworkObjectSceneChanges() - { - foreach (var entry in NetworkObjectsToSynchronizeSceneChanges) - { - entry.Value.UpdateForSceneChanges(); - } - } - private void Awake() { m_ChildNetworkBehaviours = null; @@ -3323,20 +3296,25 @@ private void Awake() /// to add this same functionality to in-scene placed NetworkObjects until we have a way to generate /// per-NetworkObject-instance unique GlobalObjectIdHash values for in-scene placed NetworkObjects. /// - internal void UpdateForSceneChanges() + internal bool UpdateForSceneChanges() { // Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager == null || NetworkManager.ShutdownInProgress || - !NetworkManager.NetworkConfig.EnableSceneManagement || IsSceneObject != false || gameObject.scene.handle == SceneOriginHandle) + !NetworkManager.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) { - return; + // Stop checking for a scene migration + return false; + } + else if (gameObject.scene.handle != SceneOriginHandle) + { + // If the scene handle has changed, then update and send notification + SceneChangedUpdate(gameObject.scene, true); } - // Otherwise, this has to be a dynamically spawned NetworkObject that has been - // migrated to a new scene. - SceneChangedUpdate(gameObject.scene, true); + // Return true (continue checking for scene migration) + return true; } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 21ac399dd5..72265a17c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -153,17 +153,6 @@ private void RemovePlayerObject(NetworkObject playerObject, bool destroyingObjec { NetworkManager.ConnectionManager.ConnectedClients[playerObject.OwnerClientId].PlayerObject = null; } - - // If we want to keep the observers, then exit early - //if (keepObservers) - //{ - // return; - //} - - //foreach (var player in m_PlayerObjects) - //{ - // player.Observers.Remove(playerObject.OwnerClientId); - //} } internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId) @@ -1161,6 +1150,8 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkObject.ApplyNetworkParenting(); NetworkObject.CheckOrphanChildren(); + AddNetworkObjectToSceneChangedUpdates(networkObject); + networkObject.InvokeBehaviourNetworkSpawn(); NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnSpawn, networkId); @@ -1196,6 +1187,50 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong } } + internal Dictionary NetworkObjectsToSynchronizeSceneChanges = new Dictionary(); + + // Pre-allocating to avoid the initial constructor hit + internal Stack CleanUpDisposedObjects = new Stack(); + + internal void AddNetworkObjectToSceneChangedUpdates(NetworkObject networkObject) + { + if ((networkObject.SceneMigrationSynchronization && NetworkManager.NetworkConfig.EnableSceneManagement) && + !NetworkObjectsToSynchronizeSceneChanges.ContainsKey(networkObject.NetworkObjectId)) + { + if (networkObject.UpdateForSceneChanges()) + { + NetworkObjectsToSynchronizeSceneChanges.Add(networkObject.NetworkObjectId, networkObject); + } + } + } + + internal void RemoveNetworkObjectFromSceneChangedUpdates(NetworkObject networkObject) + { + if ((networkObject.SceneMigrationSynchronization && NetworkManager.NetworkConfig.EnableSceneManagement) && + NetworkObjectsToSynchronizeSceneChanges.ContainsKey(networkObject.NetworkObjectId)) + { + NetworkObjectsToSynchronizeSceneChanges.Remove(networkObject.NetworkObjectId); + } + } + + internal unsafe void UpdateNetworkObjectSceneChanges() + { + foreach (var entry in NetworkObjectsToSynchronizeSceneChanges) + { + // If it fails the first update then don't add for updates + if (!entry.Value.UpdateForSceneChanges()) + { + CleanUpDisposedObjects.Push(entry.Key); + } + } + + // Clean up any NetworkObjects that no longer exist (destroyed before they should be or the like) + while (CleanUpDisposedObjects.Count > 0) + { + NetworkObjectsToSynchronizeSceneChanges.Remove(CleanUpDisposedObjects.Pop()); + } + } + internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject) { // If we are a host and sending to the host's client id, then we can skip sending ourselves the spawn message. @@ -1729,6 +1764,17 @@ internal NetworkSpawnManager(NetworkManager networkManager) NetworkManager = networkManager; } + ~NetworkSpawnManager() + { + Shutdown(); + } + + internal void Shutdown() + { + NetworkObjectsToSynchronizeSceneChanges.Clear(); + CleanUpDisposedObjects.Clear(); + } + /// /// DANGO-TODO: Until we have the CMB Server end-to-end with all features verified working via integration tests, /// I am keeping this debug toggle available. (NSS) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 64820cb626..1c65d3572d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -23,6 +23,13 @@ internal class NetworkObjectDestroyTests : NetcodeIntegrationTest public NetworkObjectDestroyTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + protected override void OnCreatePlayerPrefab() + { + var playerNetworkObject = m_PlayerPrefab.GetComponent(); + playerNetworkObject.SceneMigrationSynchronization = true; + base.OnCreatePlayerPrefab(); + } + /// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. /// @@ -133,6 +140,14 @@ public IEnumerator TestNetworkObjectClientDestroy([Values] ClientDestroyObject c yield return WaitForConditionOrTimeOut(HaveLogsBeenReceived); AssertOnTimeout($"Not all expected logs were received when destroying a {nameof(NetworkObject)} on the client side during an active session!"); } + if (m_DistributedAuthority) + { + Assert.IsFalse(m_ClientNetworkManagers[1].SpawnManager.NetworkObjectsToSynchronizeSceneChanges.ContainsKey(m_ClientNetworkObjectId), $"Player object {m_ClientNetworkObjectId} still exists within {nameof(NetworkSpawnManager.NetworkObjectsToSynchronizeSceneChanges)}!"); + } + else + { + Assert.IsFalse(m_ClientNetworkManagers[0].SpawnManager.NetworkObjectsToSynchronizeSceneChanges.ContainsKey(m_ClientNetworkObjectId), $"Player object {m_ClientNetworkObjectId} still exists within {nameof(NetworkSpawnManager.NetworkObjectsToSynchronizeSceneChanges)}!"); + } } private bool HaveLogsBeenReceived() diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index f0def620ff..32eb3be776 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -74,7 +74,7 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn([Values] DespawnMode despaw Assert.Ignore($"Test ignored as DeferDespawn is only valid with Distributed Authority mode."); } - NetworkObjectTestComponent.VerboseDebug = true; + NetworkObjectTestComponent.VerboseDebug = false; // Because despawning a client will cause it to shutdown and clean everything in the // scene hierarchy, we have to prevent one of the clients from spawning initially before // we test synchronizing late joining clients with despawned in-scene placed NetworkObjects. diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs index 084c58ac75..73349a49a1 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using NUnit.Framework; using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; @@ -309,6 +310,8 @@ private bool DataPoolVerifySceneClient(int sceneIndex, string sceneName, LoadSce return true; } + + private StringBuilder m_ErrorMsg = new StringBuilder(); /// /// Small to heavy scene loading scenario to test the dynamically generated SceneEventData objects under a load. /// Will load from 1 to 32 scenes in both single and additive ClientSynchronizationMode @@ -358,11 +361,30 @@ public IEnumerator SceneEventDataPoolSceneLoadingTest([Values(1, 2, 4, 6)] int n } yield return UnloadAllScenes(true); + Assert.IsTrue(CheckNetworkObjectsToSynchronizeSceneChanges(m_ServerNetworkManager), $"{nameof(NetworkSpawnManager.NetworkObjectsToSynchronizeSceneChanges)} validation check failure!\n {m_ErrorMsg}"); + m_ServerNetworkManager.SceneManager.OnSceneEvent -= ServerSceneManager_OnSceneEvent; + // Validate that the NetworkObjectsToSynchronizeSceneChanges does not persist entries when scenes are unloaded. foreach (var client in m_ClientNetworkManagers) { + Assert.IsTrue(CheckNetworkObjectsToSynchronizeSceneChanges(client), $"{nameof(NetworkSpawnManager.NetworkObjectsToSynchronizeSceneChanges)} validation check failure!\n {m_ErrorMsg}"); client.SceneManager.OnUnloadComplete -= SceneManager_OnUnloadComplete; } - m_ServerNetworkManager.SceneManager.OnSceneEvent -= ServerSceneManager_OnSceneEvent; + } + + private bool CheckNetworkObjectsToSynchronizeSceneChanges(NetworkManager networkManager) + { + m_ErrorMsg.Clear(); + if (networkManager.SpawnManager.NetworkObjectsToSynchronizeSceneChanges.Count > 0) + { + foreach (var entry in networkManager.SpawnManager.NetworkObjectsToSynchronizeSceneChanges) + { + if (entry.Value.IsSceneObject.HasValue && entry.Value.IsSceneObject.Value) + { + m_ErrorMsg.AppendLine($"{entry.Value.name} still exists within {nameof(NetworkSpawnManager.NetworkObjectsToSynchronizeSceneChanges)}!"); + } + } + } + return m_ErrorMsg.Length == 0; } private string m_SceneBeingUnloaded; From ac03043a4ac42f91260fcb89538a7ddd0b36d6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:23:27 +0100 Subject: [PATCH 173/236] chore: [2.X] testprojects minimal editor update (#3285) * Updated testproject editor * Updated minimalproject editor * Updated testproject-tools-integration editor --- minimalproject/Packages/manifest.json | 17 +-- minimalproject/Packages/packages-lock.json | 101 ++++++++++++++---- .../ProjectSettings/ProjectVersion.txt | 4 +- .../Packages/manifest.json | 87 +++++++-------- .../Packages/packages-lock.json | 96 +++++++++-------- .../ProjectSettings/ProjectVersion.txt | 4 +- testproject/Packages/manifest.json | 2 +- testproject/Packages/packages-lock.json | 95 ++++++---------- .../ProjectSettings/ProjectVersion.txt | 4 +- 9 files changed, 229 insertions(+), 181 deletions(-) diff --git a/minimalproject/Packages/manifest.json b/minimalproject/Packages/manifest.json index 78757b1c50..9c4ac7a7ee 100644 --- a/minimalproject/Packages/manifest.json +++ b/minimalproject/Packages/manifest.json @@ -1,8 +1,11 @@ { - "dependencies": { - "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects" - }, - "testables": [ - "com.unity.netcode.gameobjects" - ] -} \ No newline at end of file + "dependencies": { + "com.unity.ai.navigation": "2.0.5", + "com.unity.multiplayer.center": "1.0.0", + "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", + "com.unity.modules.accessibility": "1.0.0" + }, + "testables": [ + "com.unity.netcode.gameobjects" + ] +} diff --git a/minimalproject/Packages/packages-lock.json b/minimalproject/Packages/packages-lock.json index 99e56edee2..d0ad6ca622 100644 --- a/minimalproject/Packages/packages-lock.json +++ b/minimalproject/Packages/packages-lock.json @@ -1,87 +1,152 @@ { "dependencies": { + "com.unity.ai.navigation": { + "version": "2.0.5", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.ai": "1.0.0" + }, + "url": "https://packages.unity.com" + }, "com.unity.burst": { - "version": "1.6.4", + "version": "1.8.19", "depth": 2, "source": "registry", "dependencies": { - "com.unity.mathematics": "1.2.1" + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "1.2.3", + "version": "2.5.1", "depth": 2, "source": "registry", "dependencies": { - "com.unity.burst": "1.6.4", - "com.unity.test-framework": "1.1.31" + "com.unity.burst": "1.8.17", + "com.unity.test-framework": "1.4.5", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.test-framework.performance": "3.0.3" }, "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { - "version": "1.0.6", + "version": "2.0.5", "depth": 4, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.mathematics": { - "version": "1.2.5", + "version": "1.3.2", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.multiplayer.center": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + } + }, "com.unity.netcode.gameobjects": { "version": "file:../../com.unity.netcode.gameobjects", "depth": 0, "source": "local", "dependencies": { - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.3.1" + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.transport": "2.4.0" } }, "com.unity.nuget.mono-cecil": { - "version": "1.10.1", + "version": "1.11.4", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.31", + "version": "1.4.6", "depth": 3, "source": "registry", "dependencies": { - "com.unity.ext.nunit": "1.0.6", + "com.unity.ext.nunit": "2.0.3", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, + "com.unity.test-framework.performance": { + "version": "3.0.3", + "depth": 3, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.31", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, "com.unity.transport": { - "version": "1.3.1", + "version": "2.4.0", "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "1.2.3", - "com.unity.burst": "1.6.4", - "com.unity.mathematics": "1.2.5" + "com.unity.burst": "1.8.12", + "com.unity.collections": "2.2.1", + "com.unity.mathematics": "1.3.1" }, "url": "https://packages.unity.com" }, + "com.unity.modules.accessibility": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.hierarchycore": { + "version": "1.0.0", + "depth": 2, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.imgui": { "version": "1.0.0", - "depth": 4, + "depth": 2, "source": "builtin", "dependencies": {} }, "com.unity.modules.jsonserialize": { "version": "1.0.0", - "depth": 4, + "depth": 2, "source": "builtin", "dependencies": {} + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 2, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.hierarchycore": "1.0.0" + } } } } diff --git a/minimalproject/ProjectSettings/ProjectVersion.txt b/minimalproject/ProjectSettings/ProjectVersion.txt index dd7e0b04f1..307f912404 100644 --- a/minimalproject/ProjectSettings/ProjectVersion.txt +++ b/minimalproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2020.3.40f1 -m_EditorVersionWithRevision: 2020.3.40f1 (ba48d4efcef1) +m_EditorVersion: 6000.0.38f1 +m_EditorVersionWithRevision: 6000.0.38f1 (82314a941f2d) diff --git a/testproject-tools-integration/Packages/manifest.json b/testproject-tools-integration/Packages/manifest.json index d1845d58f5..86bed9f664 100644 --- a/testproject-tools-integration/Packages/manifest.json +++ b/testproject-tools-integration/Packages/manifest.json @@ -1,44 +1,47 @@ { - "registry": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates", - "dependencies": { - "com.unity.ide.rider": "3.0.7", - "com.unity.multiplayer.tools": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#release/1.1.0", - "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", - "com.unity.test-framework": "1.1.31", - "com.unity.test-framework.performance": "2.8.0-preview", - "com.unity.modules.ai": "1.0.0", - "com.unity.modules.androidjni": "1.0.0", - "com.unity.modules.animation": "1.0.0", - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.cloth": "1.0.0", - "com.unity.modules.director": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.particlesystem": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.physics2d": "1.0.0", - "com.unity.modules.screencapture": "1.0.0", - "com.unity.modules.terrain": "1.0.0", - "com.unity.modules.terrainphysics": "1.0.0", - "com.unity.modules.tilemap": "1.0.0", - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.uielements": "1.0.0", - "com.unity.modules.umbra": "1.0.0", - "com.unity.modules.unityanalytics": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.unity.modules.unitywebrequesttexture": "1.0.0", - "com.unity.modules.unitywebrequestwww": "1.0.0", - "com.unity.modules.vehicles": "1.0.0", - "com.unity.modules.video": "1.0.0", - "com.unity.modules.vr": "1.0.0", - "com.unity.modules.wind": "1.0.0", - "com.unity.modules.xr": "1.0.0" - }, - "testables": [ - "com.unity.netcode.gameobjects" - ] + "registry": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates", + "dependencies": { + "com.unity.ai.navigation": "2.0.5", + "com.unity.ide.rider": "3.0.31", + "com.unity.multiplayer.center": "1.0.0", + "com.unity.multiplayer.tools": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#release/1.1.0", + "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", + "com.unity.test-framework": "1.4.6", + "com.unity.test-framework.performance": "3.0.3", + "com.unity.modules.accessibility": "1.0.0", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + }, + "testables": [ + "com.unity.netcode.gameobjects" + ] } diff --git a/testproject-tools-integration/Packages/packages-lock.json b/testproject-tools-integration/Packages/packages-lock.json index 5e567caefa..126b02a19b 100644 --- a/testproject-tools-integration/Packages/packages-lock.json +++ b/testproject-tools-integration/Packages/packages-lock.json @@ -1,33 +1,45 @@ { "dependencies": { + "com.unity.ai.navigation": { + "version": "2.0.5", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.ai": "1.0.0" + }, + "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + }, "com.unity.burst": { - "version": "1.6.4", + "version": "1.8.19", "depth": 2, "source": "registry", "dependencies": { - "com.unity.mathematics": "1.2.1" + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.collections": { - "version": "1.2.3", + "version": "2.5.1", "depth": 2, "source": "registry", "dependencies": { - "com.unity.burst": "1.6.4", - "com.unity.test-framework": "1.1.31" + "com.unity.burst": "1.8.17", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.test-framework": "1.4.5", + "com.unity.test-framework.performance": "3.0.3" }, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.ext.nunit": { - "version": "1.0.6", + "version": "2.0.5", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.ide.rider": { - "version": "3.0.7", + "version": "3.0.31", "depth": 0, "source": "registry", "dependencies": { @@ -36,12 +48,20 @@ "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.mathematics": { - "version": "1.2.5", + "version": "1.3.2", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, + "com.unity.multiplayer.center": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + } + }, "com.unity.multiplayer.tools": { "version": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#f935904741c349dc41ba24fda6639041128e8f19", "depth": 0, @@ -60,63 +80,55 @@ "depth": 0, "source": "local", "dependencies": { - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.3.1" + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.transport": "2.4.0" } }, "com.unity.nuget.mono-cecil": { - "version": "1.10.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" - }, - "com.unity.nuget.newtonsoft-json": { - "version": "2.0.0", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" - }, - "com.unity.profiling.core": { - "version": "1.0.0-pre.1", + "version": "1.11.4", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.test-framework": { - "version": "1.1.31", + "version": "1.4.6", "depth": 0, "source": "registry", "dependencies": { - "com.unity.ext.nunit": "1.0.6", + "com.unity.ext.nunit": "2.0.3", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.test-framework.performance": { - "version": "2.8.0-preview", + "version": "3.0.3", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.0", + "com.unity.test-framework": "1.1.31", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, "com.unity.transport": { - "version": "1.3.1", + "version": "2.4.0", "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "1.2.3", - "com.unity.burst": "1.6.4", - "com.unity.mathematics": "1.2.5" + "com.unity.collections": "2.2.1", + "com.unity.burst": "1.8.12", + "com.unity.mathematics": "1.3.1" }, "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" }, + "com.unity.modules.accessibility": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.ai": { "version": "1.0.0", "depth": 0, @@ -164,6 +176,12 @@ "com.unity.modules.animation": "1.0.0" } }, + "com.unity.modules.hierarchycore": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.imageconversion": { "version": "1.0.0", "depth": 0, @@ -253,17 +271,7 @@ "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.uielementsnative": "1.0.0" - } - }, - "com.unity.modules.uielementsnative": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" + "com.unity.modules.hierarchycore": "1.0.0" } }, "com.unity.modules.umbra": { diff --git a/testproject-tools-integration/ProjectSettings/ProjectVersion.txt b/testproject-tools-integration/ProjectSettings/ProjectVersion.txt index dd7e0b04f1..307f912404 100644 --- a/testproject-tools-integration/ProjectSettings/ProjectVersion.txt +++ b/testproject-tools-integration/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2020.3.40f1 -m_EditorVersionWithRevision: 2020.3.40f1 (ba48d4efcef1) +m_EditorVersion: 6000.0.38f1 +m_EditorVersionWithRevision: 6000.0.38f1 (82314a941f2d) diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 1a765a9f04..50e5a2a32e 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -10,7 +10,7 @@ "com.unity.package-validation-suite": "0.49.0-preview", "com.unity.services.authentication": "3.4.0", "com.unity.services.core": "1.14.0", - "com.unity.test-framework": "1.4.5", + "com.unity.test-framework": "1.4.6", "com.unity.test-framework.performance": "3.0.3", "com.unity.timeline": "1.8.7", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 7b289e9221..4788651e18 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,22 +1,22 @@ { "dependencies": { "com.unity.addressables": { - "version": "2.0.8", + "version": "2.2.2", "depth": 0, "source": "registry", "dependencies": { - "com.unity.scriptablebuildpipeline": "2.1.2", + "com.unity.profiling.core": "1.0.2", "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.profiling.core": "1.0.2" + "com.unity.scriptablebuildpipeline": "2.1.4", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.ai.navigation": { - "version": "2.0.0", + "version": "2.0.5", "depth": 0, "source": "registry", "dependencies": { @@ -25,7 +25,7 @@ "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.8.13", + "version": "1.8.19", "depth": 2, "source": "registry", "dependencies": { @@ -35,20 +35,20 @@ "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "2.3.1", + "version": "2.6.0", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "2.4.0", + "version": "2.5.1", "depth": 2, "source": "registry", "dependencies": { - "com.unity.burst": "1.8.12", + "com.unity.burst": "1.8.17", + "com.unity.test-framework": "1.4.5", "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.test-framework": "1.4.3", "com.unity.test-framework.performance": "3.0.3" }, "url": "https://packages.unity.com" @@ -61,7 +61,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.28", + "version": "3.0.31", "depth": 0, "source": "registry", "dependencies": { @@ -91,7 +91,7 @@ "source": "local", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.2.1" + "com.unity.transport": "2.4.0" } }, "com.unity.nuget.mono-cecil": { @@ -125,84 +125,53 @@ "url": "https://packages.unity.com" }, "com.unity.scriptablebuildpipeline": { - "version": "2.1.2", + "version": "2.1.4", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.services.authentication": { - "version": "3.3.1", + "version": "3.4.0", "depth": 0, "source": "registry", "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.services.core": "1.14.0", "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.services.core": "1.12.5", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.ugui": "1.0.0" + "com.unity.modules.unitywebrequest": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.services.core": { - "version": "1.12.5", + "version": "1.14.0", "depth": 0, "source": "registry", "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.androidjni": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.qos": { - "version": "1.3.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.12.4", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.services.authentication": "2.0.0", - "com.unity.collections": "1.2.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.relay": { - "version": "1.0.5", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.4.0", - "com.unity.services.authentication": "2.0.0", - "com.unity.services.qos": "1.1.0", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.unity.modules.unitywebrequesttexture": "1.0.0", - "com.unity.modules.unitywebrequestwww": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.transport": "1.3.0" + "com.unity.modules.unitywebrequest": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.sysroot": { - "version": "2.0.7", + "version": "2.0.10", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.sysroot.linux-x86_64": { - "version": "2.0.6", + "version": "2.0.9", "depth": 1, "source": "registry", "dependencies": { - "com.unity.sysroot": "2.0.7" + "com.unity.sysroot": "2.0.10" }, "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.4.4", + "version": "1.4.6", "depth": 0, "source": "registry", "dependencies": { @@ -223,34 +192,34 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.8.6", + "version": "1.8.7", "depth": 0, "source": "registry", "dependencies": { + "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", - "com.unity.modules.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.6", + "version": "2.0.10", "depth": 0, "source": "registry", "dependencies": { - "com.unity.sysroot": "2.0.7", - "com.unity.sysroot.linux-x86_64": "2.0.6" + "com.unity.sysroot": "2.0.10", + "com.unity.sysroot.linux-x86_64": "2.0.9" }, "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "2.2.1", + "version": "2.4.0", "depth": 1, "source": "registry", "dependencies": { + "com.unity.burst": "1.8.12", "com.unity.collections": "2.2.1", - "com.unity.burst": "1.8.8", "com.unity.mathematics": "1.3.1" }, "url": "https://packages.unity.com" diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index d1c9097a6c..307f912404 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.32f1 -m_EditorVersionWithRevision: 6000.0.32f1 (b2e806cf271c) +m_EditorVersion: 6000.0.38f1 +m_EditorVersionWithRevision: 6000.0.38f1 (82314a941f2d) From 91ebe9652baa336a936a6d7f8be6e9ba18d5c3f0 Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:43:00 +0100 Subject: [PATCH 174/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.37 (develop-2.0.0) (#3290) * chore(deps): update dependency recipeengine.modules.wrench to 0.10.37 * Regenerated recipes --------- Co-authored-by: unity-renovate[bot] <120015202+unity-renovate[bot]@users.noreply.github.com> Co-authored-by: michalChrobot --- ...rastructure-instability-detection-linux.sh | 37 +++++ ...nfrastructure-instability-detection-mac.sh | 37 +++++ ...frastructure-instability-detection-win.cmd | 11 ++ .yamato/wrench/api-validation-jobs.yml | 11 +- .yamato/wrench/package-pack-jobs.yml | 12 +- .yamato/wrench/preview-a-p-v.yml | 101 +++++-------- .yamato/wrench/promotion-jobs.yml | 22 ++- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 135 +++++++----------- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 11 files changed, 193 insertions(+), 179 deletions(-) create mode 100644 .yamato/generated-scripts/infrastructure-instability-detection-linux.sh create mode 100644 .yamato/generated-scripts/infrastructure-instability-detection-mac.sh create mode 100644 .yamato/generated-scripts/infrastructure-instability-detection-win.cmd diff --git a/.yamato/generated-scripts/infrastructure-instability-detection-linux.sh b/.yamato/generated-scripts/infrastructure-instability-detection-linux.sh new file mode 100644 index 0000000000..2380121236 --- /dev/null +++ b/.yamato/generated-scripts/infrastructure-instability-detection-linux.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# This is an auto-generated script. Do not edit manually! +set -x + +set -e +if [ -f "infrastructure_instability_detection_standalone.zip" ]; then + echo "removed existing archive infrastructure_instability_detection_standalone.zip" + rm "infrastructure_instability_detection_standalone.zip" || true +fi + +if [ -d "infrastructure_instability_detection_standalone" ]; then + echo "removed existing directory infrastructure_instability_detection_standalone/" + rm -rf "infrastructure_instability_detection_standalone" || true +fi + +echo "downloading and extracting infrastructure_instability_detection_standalone@1.0.0" +curl -fs "https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/1.0.0/ubuntu.zip" --output "infrastructure_instability_detection_standalone.zip" --retry 5 || true + +if [ -d "infrastructure_instability_detection" ]; then + echo "removing infrastructure_instability_detection folder to avoid name clash" + rm -rf infrastructure_instability_detection/ || true +fi + +unzip -qo "infrastructure_instability_detection_standalone.zip" && rm "infrastructure_instability_detection_standalone.zip" || true + +echo "downloading and extracting patterns" +curl -fs "https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/patterns.zip" --output patterns.zip --retry 5 || true + +if [ -d "patterns" ]; then + echo "removing patterns folder to avoid name clash" + rm -rf patterns/ || true +fi + +unzip -q patterns.zip && rm patterns.zip || true + +echo "running '$(pwd)/infrastructure_instability_detection'" +./infrastructure_instability_detection || true diff --git a/.yamato/generated-scripts/infrastructure-instability-detection-mac.sh b/.yamato/generated-scripts/infrastructure-instability-detection-mac.sh new file mode 100644 index 0000000000..e37062e974 --- /dev/null +++ b/.yamato/generated-scripts/infrastructure-instability-detection-mac.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# This is an auto-generated script. Do not edit manually! +set -x + +set -e +if [ -f "infrastructure_instability_detection_standalone.zip" ]; then + echo "removed existing archive infrastructure_instability_detection_standalone.zip" + rm "infrastructure_instability_detection_standalone.zip" || true +fi + +if [ -d "infrastructure_instability_detection_standalone" ]; then + echo "removed existing directory infrastructure_instability_detection_standalone/" + rm -rf "infrastructure_instability_detection_standalone" || true +fi + +echo "downloading and extracting infrastructure_instability_detection_standalone@1.0.0" +curl -fs "https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/1.0.0/macos.zip" --output "infrastructure_instability_detection_standalone.zip" --retry 5 || true + +if [ -d "infrastructure_instability_detection" ]; then + echo "removing infrastructure_instability_detection folder to avoid name clash" + rm -rf infrastructure_instability_detection/ || true +fi + +unzip -qo "infrastructure_instability_detection_standalone.zip" && rm "infrastructure_instability_detection_standalone.zip" || true + +echo "downloading and extracting patterns" +curl -fs "https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/patterns.zip" --output patterns.zip --retry 5 || true + +if [ -d "patterns" ]; then + echo "removing patterns folder to avoid name clash" + rm -rf patterns/ || true +fi + +unzip -q patterns.zip && rm patterns.zip || true + +echo "running '$(pwd)/infrastructure_instability_detection'" +./infrastructure_instability_detection || true diff --git a/.yamato/generated-scripts/infrastructure-instability-detection-win.cmd b/.yamato/generated-scripts/infrastructure-instability-detection-win.cmd new file mode 100644 index 0000000000..64513447d2 --- /dev/null +++ b/.yamato/generated-scripts/infrastructure-instability-detection-win.cmd @@ -0,0 +1,11 @@ +@echo on +rem This is an auto-generated script. Do not edit manually! + +curl -fs "https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/1.0.0/windows.zip" --output "infrastructure_instability_detection_standalone.zip" --retry 5 +IF EXIST "infrastructure_instability_detection" rmdir /s /q infrastructure_instability_detection +powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('infrastructure_instability_detection_standalone.zip', '.'); }" && DEL "infrastructure_instability_detection_standalone.zip" +curl -fs "https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/patterns.zip" --output patterns.zip --retry 5 +IF EXIST "patterns" rmdir /s /q patterns +powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('patterns.zip', '.'); }" && DEL "patterns.zip" +infrastructure_instability_detection +exit /b 0 diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 89fd3b31d8..e657387536 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -9,7 +9,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -29,10 +29,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: timeout: 2 retries: 0 after: - - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" - timeout: 10 - retries: 1 + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: Crash Dumps: paths: @@ -50,11 +47,11 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 triggers: expression: push.branch match "^release/.*" cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 6c51944ed4..be85fdab2b 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -12,25 +12,17 @@ package_pack_-_netcode_gameobjects: - command: npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm timeout: 20 retries: 10 - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip - - command: 7z x -aoa wrench-localapv.zip - - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - - command: python PythonScripts/print_machine_info.py - command: upm-ci package pack --package-path com.unity.netcode.gameobjects - command: cp upm-ci~/packages/packages.json upm-ci~/packages/com.unity.netcode.gameobjects_packages.json after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: packages: paths: - upm-ci~/packages/**/* variables: UPMCI_ACK_LARGE_PACKAGE: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index b9a00ccacc..2b2d966538 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -19,7 +19,7 @@ all_preview_apv_jobs: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -29,7 +29,7 @@ preview_apv_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -42,10 +42,7 @@ preview_apv_-_6000_0_-_macos: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh artifacts: Crash Dumps: paths: @@ -73,10 +70,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -86,7 +83,7 @@ preview_apv_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -99,10 +96,7 @@ preview_apv_-_6000_0_-_ubuntu: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: Crash Dumps: paths: @@ -130,10 +124,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -144,7 +138,7 @@ preview_apv_-_6000_0_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -157,10 +151,7 @@ preview_apv_-_6000_0_-_windows: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: python PythonScripts/editor_manifest_validator.py --version=6000.0 --wrench-config=.yamato/wrench/wrench_config.json after: - - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" - timeout: 10 - retries: 1 + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: Crash Dumps: paths: @@ -188,10 +179,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -201,7 +192,7 @@ preview_apv_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -214,10 +205,7 @@ preview_apv_-_6000_1_-_macos: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.1 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh artifacts: Crash Dumps: paths: @@ -245,10 +233,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -258,7 +246,7 @@ preview_apv_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -271,10 +259,7 @@ preview_apv_-_6000_1_-_ubuntu: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.1 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: Crash Dumps: paths: @@ -302,10 +287,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -316,7 +301,7 @@ preview_apv_-_6000_1_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -329,10 +314,7 @@ preview_apv_-_6000_1_-_windows: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.1 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: python PythonScripts/editor_manifest_validator.py --version=6000.1 --wrench-config=.yamato/wrench/wrench_config.json after: - - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" - timeout: 10 - retries: 1 + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: Crash Dumps: paths: @@ -360,10 +342,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -373,7 +355,7 @@ preview_apv_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -386,10 +368,7 @@ preview_apv_-_6000_2_-_macos: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.2 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh artifacts: Crash Dumps: paths: @@ -417,10 +396,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -430,7 +409,7 @@ preview_apv_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -443,10 +422,7 @@ preview_apv_-_6000_2_-_ubuntu: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.2 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: echo 'Skipping Editor Manifest Validator as it is only supported on Windows' after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: Crash Dumps: paths: @@ -474,10 +450,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -488,7 +464,7 @@ preview_apv_-_6000_2_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -501,10 +477,7 @@ preview_apv_-_6000_2_-_windows: - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.2 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - command: python PythonScripts/editor_manifest_validator.py --version=6000.2 --wrench-config=.yamato/wrench/wrench_config.json after: - - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" - timeout: 10 - retries: 1 + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: Crash Dumps: paths: @@ -532,8 +505,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 71e0b276e1..ff7853916d 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -9,7 +9,7 @@ publish_dry_run_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -18,10 +18,7 @@ publish_dry_run_netcode_gameobjects: - command: python PythonScripts/ignore_existing_package_failure.py - command: python PythonScripts/run_publish_if_any_package_left.py --dry-run after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: logs: paths: @@ -103,13 +100,13 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 triggers: expression: push.branch match "^release/.*" cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -119,7 +116,7 @@ publish_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -128,10 +125,7 @@ publish_netcode_gameobjects: - command: python PythonScripts/ignore_existing_package_failure.py - command: python PythonScripts/run_publish_if_any_package_left.py after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: logs: paths: @@ -213,8 +207,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index 2998d6cb5d..8494f3931c 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index fa10d4953d..7669869309 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -9,7 +9,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -21,7 +21,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -30,13 +30,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: timeout: 10 retries: 0 - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh artifacts: Crash Dumps: paths: @@ -63,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -78,7 +75,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -90,7 +87,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -99,13 +96,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: timeout: 10 retries: 0 - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: Crash Dumps: paths: @@ -132,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -147,7 +141,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -159,7 +153,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -168,13 +162,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: timeout: 10 retries: 0 - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" - timeout: 10 - retries: 1 + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: Crash Dumps: paths: @@ -201,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -216,7 +207,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -228,7 +219,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -237,13 +228,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: timeout: 10 retries: 0 - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh artifacts: Crash Dumps: paths: @@ -270,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -285,7 +273,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -297,7 +285,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -306,13 +294,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: timeout: 10 retries: 0 - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: Crash Dumps: paths: @@ -339,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -354,7 +339,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -366,7 +351,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -375,13 +360,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: timeout: 10 retries: 0 - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" - timeout: 10 - retries: 1 + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: Crash Dumps: paths: @@ -408,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -423,7 +405,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -435,7 +417,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -444,13 +426,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: timeout: 10 retries: 0 - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh macos 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-mac.sh artifacts: Crash Dumps: paths: @@ -477,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -492,7 +471,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -504,7 +483,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -513,13 +492,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: timeout: 10 retries: 0 - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.sh --output run_standalone_instability_detection-latest.sh --retry 5 || exit 0 - - command: bash ./run_standalone_instability_detection-latest.sh ubuntu 0.5.1 || exit 0 - timeout: 10 - retries: 1 + - command: bash .yamato/generated-scripts/infrastructure-instability-detection-linux.sh artifacts: Crash Dumps: paths: @@ -546,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects @@ -561,7 +537,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_236616cc910608f3016a29947795c2e53f0c0dbfa45128d551397825ed65689e.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -573,7 +549,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: retries: 1 - command: echo No internal packages to add. - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp - timeout: 20 + timeout: 40 retries: 0 - command: upm-pvp require "pkgprom-promote -PVP-29-2 rme" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 5 @@ -582,13 +558,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: timeout: 10 retries: 0 - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" - timeout: 30 + timeout: 40 retries: 1 after: - - command: cmd.exe /c "curl -s https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/setup/run_standalone_instability_detection-latest.bat --output run_standalone_instability_detection-latest.bat --retry 5 || exit 0" - - command: cmd.exe /c "run_standalone_instability_detection-latest.bat 0.5.1 || exit 0" - timeout: 10 - retries: 1 + - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd artifacts: Crash Dumps: paths: @@ -615,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.36.0 + UPMPVP_CONTEXT_WRENCH: 0.10.37.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.36.0 + Wrench: 0.10.37.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 10b43c396d..8826b74083 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.36.0", + "wrench_version": "0.10.37.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index a60fa2f818..47b1bfe16b 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From 191251dd4ec6e9efc31d1390f3aec24ce55745bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:04:19 +0100 Subject: [PATCH 175/236] docs: fixes of PVP-150-1 [2.X] (#3223) * Fixes of PVP-150-1 * Remaining fixes * update Updating some of the missing XML API documentation. * Deleted fixed pvp 150-1 exceptions * Corrected PVP exceptions detection * Corrected results from pvp checks location * Added new 150-1 exceptions to verify pvp checks * Removed one pvp error for checks * Moved pvpException file and updated package test job * corrected path * removed pvp check from package-pack * Corrected package-pack * Modified win command * Added missing artifact * Corrected utr command * Removed unnecessary extension of pvpProfiles * Added new 151-1 exceptions * Corrected last 150-1 exceptions * Added last 151-1 exceptions * Corrected error formatting --------- Co-authored-by: NoelStephensUnity --- .yamato/package-pack.yml | 12 +- .yamato/package-tests.yml | 20 +- .../Editor/NetcodeEditorBase.cs | 2 +- .../Components/AnticipatedNetworkTransform.cs | 26 +- .../Runtime/Components/HalfVector3.cs | 2 +- .../BufferedLinearInterpolator.cs | 2 +- .../Runtime/Components/NetworkTransform.cs | 21 +- .../Runtime/Configuration/NetworkConfig.cs | 13 +- .../Configuration/NetworkPrefabsList.cs | 4 +- .../Runtime/Core/NetworkBehaviour.cs | 21 +- .../Runtime/Core/NetworkManager.cs | 18 +- .../Runtime/Core/NetworkObject.cs | 13 +- .../Exceptions/InvalidParentException.cs | 10 +- ...rializationForGenericParameterAttribute.cs | 16 +- .../Messaging/RpcTargets/BaseRpcTarget.cs | 13 + .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 47 +- .../AnticipatedNetworkVariable.cs | 22 +- .../Collections/NetworkList.cs | 42 +- .../NetworkVariable/NetworkVariable.cs | 4 +- .../NetworkVariable/NetworkVariableBase.cs | 4 +- .../NetworkVariableSerialization.cs | 22 +- .../Serialization/TypedILPPInitializers.cs | 61 +- .../UserNetworkVariableSerialization.cs | 3 +- .../SceneManagement/NetworkSceneManager.cs | 66 +- .../Runtime/SceneManagement/SceneEventData.cs | 2 +- .../Runtime/Serialization/BufferSerializer.cs | 6 +- .../Runtime/Serialization/BytePacker.cs | 2 +- .../Runtime/Serialization/ByteUnpacker.cs | 4 +- .../Runtime/Serialization/FastBufferReader.cs | 67 +- .../Runtime/Serialization/FastBufferWriter.cs | 96 ++- .../ForceNetworkSerializeByMemcpy.cs | 2 +- .../Runtime/Serialization/IReaderWriter.cs | 9 +- .../NetworkBehaviourReference.cs | 2 +- .../Serialization/NetworkObjectReference.cs | 8 +- .../Runtime/Spawning/NetworkPrefabHandler.cs | 13 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 9 + .../Runtime/Timing/NetworkTime.cs | 7 +- .../Runtime/Timing/NetworkTimeSystem.cs | 2 +- .../Runtime/Transports/NetworkTransport.cs | 21 +- .../Runtime/Transports/UTP/UnityTransport.cs | 13 +- .../Runtime/NetcodeIntegrationTest.cs | 10 +- .../Runtime/NetcodeIntegrationTestHelpers.cs | 27 +- .../Runtime/NetworkManagerHelper.cs | 4 +- .../Runtime/NetworkVariableHelper.cs | 2 +- .../TestHelpers/Runtime/TimeoutHelper.cs | 2 +- ...orkVariableBaseInitializesWhenPersisted.cs | 7 +- .../pvpExceptions.json => pvpExceptions.json | 739 ++++++------------ ...tions.json.meta => pvpExceptions.json.meta | 0 48 files changed, 673 insertions(+), 845 deletions(-) rename com.unity.netcode.gameobjects/pvpExceptions.json => pvpExceptions.json (69%) rename com.unity.netcode.gameobjects/pvpExceptions.json.meta => pvpExceptions.json.meta (100%) diff --git a/.yamato/package-pack.yml b/.yamato/package-pack.yml index cc85801042..a9ab902847 100644 --- a/.yamato/package-pack.yml +++ b/.yamato/package-pack.yml @@ -12,12 +12,16 @@ package_pack_-_ngo_{{ platform.name }}: image: {{ platform.image }} flavor: {{ platform.flavor }} timeout: 0.25 + variables: + XRAY_PROFILE: "supported ./pvpExceptions.json" commands: - - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.it.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci package pack --package-path com.unity.netcode.gameobjects - - upm-pvp xray --packages "upm-ci~/packages/*.tgz" --results upm-ci~/xray - - upm-pvp require "supported rme com.unity.netcode.gameobjects/pvpExceptions.json" --allow-missing --results upm-ci~/xray --exemptions upm-ci~/xray/new-exemptions.json + - upm-pvp pack "com.unity.netcode.gameobjects" --output upm-ci~/packages + - upm-pvp xray --packages "upm-ci~/packages/com.unity.netcode.gameobjects*.tgz" --results pvp-results + - upm-pvp require {% if platform.name == "win" %}"%XRAY_PROFILE%"{% else %}"$XRAY_PROFILE"{% endif %} --results pvp-results --allow-missing artifacts: + logs: + paths: + - "pvp-results/*" packages: paths: - "upm-ci~/**" diff --git a/.yamato/package-tests.yml b/.yamato/package-tests.yml index 0b3eb31109..79815ebc16 100644 --- a/.yamato/package-tests.yml +++ b/.yamato/package-tests.yml @@ -12,14 +12,30 @@ package_test_-_ngo_{{ editor }}_{{ platform.name }}: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} + variables: + XRAY_PROFILE: "supported ./pvpExceptions.json" + UNITY_EXT_LOGGING: 1 commands: - - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait - - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %}upm-ci package test -u {{ editor }} --package-path com.unity.netcode.gameobjects --type package-tests --extra-utr-arg="--extra-editor-arg=-testCategory --extra-editor-arg=!Performance --reruncount=1 --clean-library-on-rerun" + # Platform specific UTR setup + - | +{% if platform.name == "win" %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat +{% else %} + curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr +{% endif %} + + # Validate packages. + - upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects" --results pvp-results + - upm-pvp require {% if platform.name == "win" %}"%XRAY_PROFILE%"{% else %}"$XRAY_PROFILE"{% endif %} --results pvp-results + # Run UTR to test packages. + - upm-pvp create-test-project test-project --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects" --unity .Editor + - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %} {% if platform.name == "win" %} utr.bat {% else %} ./utr {% endif %} --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --artifacts-path=test-results "--ff={ops.upmpvpevidence.enable=true}" --extra-editor-arg=-testCategory --extra-editor-arg=!Performance --reruncount=1 --clean-library-on-rerun artifacts: logs: paths: - "upm-ci~/test-results/**/*" + - "pvp-results/*" dependencies: - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} {% endfor -%} diff --git a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs index 5112065e80..4b8a351a35 100644 --- a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.Editor [CanEditMultipleObjects] public partial class NetcodeEditorBase : UnityEditor.Editor where TT : MonoBehaviour { - /// + /// public virtual void OnEnable() { } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index 21d3c054bf..d99bd4dc2f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -13,24 +13,24 @@ namespace Unity.Netcode.Components /// /// /// - /// Snap: In this mode (with set to + /// Snap: In this mode (with set to /// and no callback), /// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value, - /// resulting in a "snap" to the new value if it is different from the anticipated value. + /// resulting in a "snap" to the new value if it is different from the anticipated value. /// - /// Smooth: In this mode (with set to + /// Smooth: In this mode (with set to /// and an callback that calls /// from the anticipated value to the authority value with an appropriate /// -style smooth function), when a more up-to-date value is received from the authority, - /// it will interpolate over time from an incorrect anticipated value to the correct authoritative value. + /// it will interpolate over time from an incorrect anticipated value to the correct authoritative value. /// - /// Constant Reanticipation: In this mode (with set to + /// Constant Reanticipation: In this mode (with set to /// and an that calculates a /// new anticipated value based on the current authoritative value), when a more up-to-date value is received from /// the authority, user code calculates a new anticipated value, possibly calling to interpolate /// between the previous anticipation and the new anticipation. This is useful for values that change frequently and /// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply - /// need a one-time anticipation when the user performs that action. + /// need a one-time anticipation when the user performs that action. /// /// /// @@ -138,7 +138,7 @@ public bool ShouldReanticipate /// Anticipate that, at the end of one round trip to the server, this transform will be in the given /// /// - /// + /// The anticipated position public void AnticipateMove(Vector3 newPosition) { if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) @@ -164,7 +164,7 @@ public void AnticipateMove(Vector3 newPosition) /// Anticipate that, at the end of one round trip to the server, this transform will have the given /// /// - /// + /// The anticipated rotation public void AnticipateRotate(Quaternion newRotation) { if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) @@ -190,7 +190,7 @@ public void AnticipateRotate(Quaternion newRotation) /// Anticipate that, at the end of one round trip to the server, this transform will have the given /// /// - /// + /// The anticipated scale public void AnticipateScale(Vector3 newScale) { if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) @@ -216,7 +216,7 @@ public void AnticipateScale(Vector3 newScale) /// Anticipate that, at the end of one round trip to the server, the transform will have the given /// /// - /// + /// The anticipated transform state public void AnticipateState(TransformState newState) { if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening) @@ -463,9 +463,9 @@ public override void OnDestroy() /// over of real time. The duration uses /// , so it is affected by . /// - /// - /// - /// + /// Starting transform state + /// Target transform state + /// Interpolation time in seconds public void Smooth(TransformState from, TransformState to, float durationSeconds) { var transform_ = transform; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs index 8bba3d4170..ce696ec965 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs @@ -129,7 +129,7 @@ public void UpdateFrom(ref Vector3 vector3) /// Constructor /// /// The initial axial values (converted to half floats) when instantiated. - /// The axis to synchronize. + /// The axis to synchronize. public HalfVector3(Vector3 vector3, bool3 axisToSynchronize) { Axis = half3.zero; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index e628c7cab2..d5fbaaa5cf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -400,7 +400,7 @@ protected internal override void OnConvertTransformSpace(Transform transform, bo } /// - /// A implementation. + /// A implementation. /// public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7dd677b135..abbf1ddeb0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1482,9 +1482,8 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) /// /// If a derived class overrides this, then make sure to invoke this base method! /// - /// - /// - /// the clientId being synchronized (both reading and writing) + /// The serializer type for buffer operations + /// The buffer serializer used for network state synchronization protected override void OnSynchronize(ref BufferSerializer serializer) { var targetClientId = m_TargetIdBeingSynchronized; @@ -3200,7 +3199,7 @@ protected virtual void OnInitialize(ref NetworkTransformState replicatedState) /// This method is only invoked by the owner /// Use: OnInitialize(ref NetworkTransformState replicatedState) to be notified on all instances /// - /// + /// The NetworkVariable containing the protected virtual void OnInitialize(ref NetworkVariable replicatedState) { @@ -3527,11 +3526,11 @@ internal override void InternalOnNetworkObjectParentChanged(NetworkObject parent /// The parameters are broken up into pos / rot / scale on purpose so that the caller can perturb /// just the desired one(s) /// - /// new position to move to. Can be null - /// new rotation to rotate to. Can be null + /// new position to move to. Can be null + /// new rotation to rotate to. Can be null /// new scale to scale to. Can be null /// When true (the default) the will not be teleported and, if enabled, will interpolate. When false the will teleport/apply the parameters provided immediately. - /// + /// Thrown when the function is called on non-spawned object or, when it's called without proper authority public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool teleportDisabled = true) { if (!IsSpawned) @@ -3667,10 +3666,10 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool /// This is intended to be used on already spawned objects, for setting the position of a dynamically spawned object just apply the transform values prior to spawning.
/// With player objects, override the method and have the authority make adjustments to the transform prior to invoking base.OnNetworkSpawn. /// - /// new position to move to. - /// new rotation to rotate to. + /// new position to move to. + /// new rotation to rotate to. /// new scale to scale to. - /// + /// Thrown when called from a non-authoritative context (client without ownership) public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale) { if (!CanCommitToTransform) @@ -3732,7 +3731,7 @@ private void UpdateInterpolation() } } - /// + /// /// /// If you override this method, be sure that: /// - Non-authority always invokes this base class method. diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index a9668084bd..fbbbf86ebb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -204,7 +204,7 @@ internal void OnValidate() /// /// Returns a base64 encoded version of the configuration /// - /// + /// base64 encoded string containing the serialized network configuration public string ToBase64() { NetworkConfig config = this; @@ -271,8 +271,8 @@ internal void ClearConfigHash() /// /// Gets a SHA256 hash of parts of the NetworkConfig instance /// - /// - /// + /// When true, caches the computed hash value for future retrievals, when false, always recomputes the hash + /// A 64-bit hash value representing the configuration state public ulong GetConfig(bool cache = true) { if (m_ConfigHash != null && cache) @@ -316,8 +316,11 @@ public ulong GetConfig(bool cache = true) /// /// Compares a SHA256 hash with the current NetworkConfig instances hash /// - /// - /// + /// The 64-bit hash value to compare against this configuration's hash + /// + /// True if the hashes match, indicating compatible configurations. + /// False if the hashes differ, indicating potentially incompatible configurations. + /// public bool CompareConfig(ulong hash) { return hash == GetConfig(); diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabsList.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabsList.cs index 1c4719120a..5003a2fe4e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabsList.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabsList.cs @@ -38,7 +38,7 @@ public class NetworkPrefabsList : ScriptableObject /// Adds a prefab to the prefab list. Performing this here will apply the operation to all /// s that reference this list. ///
- /// + /// The NetworkPrefab to add to the shared list public void Add(NetworkPrefab prefab) { List.Add(prefab); @@ -49,7 +49,7 @@ public void Add(NetworkPrefab prefab) /// Removes a prefab from the prefab list. Performing this here will apply the operation to all /// s that reference this list. ///
- /// + /// The NetworkPrefab to remove from the shared list public void Remove(NetworkPrefab prefab) { List.Remove(prefab); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index da6b35e001..9629550313 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -529,12 +529,12 @@ internal bool IsBehaviourEditable() m_NetworkObject.NetworkManager.IsServer; } - /// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in - /// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not - /// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you - /// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get - /// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do - /// how NetworkObject works but it was close to the release and too risky to change + // TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in + // its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not + // (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you + // need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get + // the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do + // how NetworkObject works but it was close to the release and too risky to change /// /// Gets the NetworkObject that owns this NetworkBehaviour instance. /// @@ -826,7 +826,7 @@ internal void InternalOnNetworkDespawn() /// /// In client-server contexts, this method is invoked on both the server and the local client of the owner when ownership is assigned. - /// In distributed authority contexts, this method is only invoked on the local client that has been assigned ownership of the associated . + /// In distributed authority contexts, this method is only invoked on the local client that has been assigned ownership of the associated . /// public virtual void OnGainedOwnership() { } @@ -863,7 +863,7 @@ internal void InternalOnOwnershipChanged(ulong previous, ulong current) /// /// In client-server contexts, this method is invoked on the local client when it loses ownership of the associated /// and on the server when any client loses ownership. - /// In distributed authority contexts, this method is only invoked on the local client that has lost ownership of the associated . + /// In distributed authority contexts, this method is only invoked on the local client that has lost ownership of the associated . /// public virtual void OnLostOwnership() { } @@ -1348,8 +1348,8 @@ internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) /// /// Gets the local instance of a NetworkObject with a given NetworkId. /// - /// - /// + /// The unique network identifier of the NetworkObject to retrieve + /// The NetworkObject instance if found, null if no object exists with the specified networkId protected NetworkObject GetNetworkObject(ulong networkId) { return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null; @@ -1372,7 +1372,6 @@ protected NetworkObject GetNetworkObject(ulong networkId) /// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer /// is in read mode or write mode. /// - /// the relative client identifier being synchronized protected virtual void OnSynchronize(ref BufferSerializer serializer) where T : IReaderWriter { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 54e5b648b3..9f257fb5e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -810,14 +810,12 @@ public struct ConnectionApprovalRequest /// /// This callback is invoked once the local server is stopped. /// - /// The first parameter of this event will be set to when stopping a host instance and when stopping a server instance. public event Action OnServerStopped = null; /// /// The callback to invoke once the local client stops /// /// The parameter states whether the client was running in host mode - /// The first parameter of this event will be set to when stopping the host client and when stopping a standard client instance. public event Action OnClientStopped = null; /// @@ -1108,14 +1106,14 @@ private void OnEnable() /// /// /// - /// - /// + /// The GameObject prefab to register for network spawning + /// Thrown when the prefab is invalid or already registered public void AddNetworkPrefab(GameObject prefab) => PrefabHandler.AddNetworkPrefab(prefab); /// /// /// - /// + /// The GameObject prefab to unregister from network spawning public void RemoveNetworkPrefab(GameObject prefab) => PrefabHandler.RemoveNetworkPrefab(prefab); /// @@ -1127,7 +1125,6 @@ private void OnEnable() /// and thus should be large enough to ensure it can hold each message type. /// This value defaults to 1296. /// - /// public int MaximumTransmissionUnitSize { set => MessageManager.NonFragmentedMessageMaxSize = value & ~7; // Round down to nearest word aligned size @@ -1139,8 +1136,8 @@ public int MaximumTransmissionUnitSize /// This determines the maximum size of a message batch that can be sent to that client. /// If not set for any given client, will be used instead. /// - /// - /// + /// The unique identifier of the client peer + /// The MTU size in bytes for this specific peer public void SetPeerMTU(ulong clientId, int size) { MessageManager.PeerMTUSizes[clientId] = size; @@ -1150,8 +1147,8 @@ public void SetPeerMTU(ulong clientId, int size) /// Queries the current MTU size for a client. /// If no MTU has been set for that client, will return ///
- /// - /// + /// he unique identifier of the client peer + /// The MTU size in bytes for the specified peer. If no custom MTU has been set for this peer, returns the global value. public int GetPeerMTU(ulong clientId) { if (MessageManager.PeerMTUSizes.TryGetValue(clientId, out var ret)) @@ -1166,7 +1163,6 @@ public int GetPeerMTU(ulong clientId) /// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery. /// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size. ///
- /// public int MaximumFragmentedMessageSize { set => MessageManager.FragmentedMessageMaxSize = value; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 332b1304ec..2b0900a166 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -58,7 +58,7 @@ public uint PrefabIdHash } /// - /// All component instances associated with a component instance. + /// All component instances associated with a component instance. /// /// /// When parented, all child component instances under a component instance that do not have @@ -608,7 +608,7 @@ public enum OwnershipPermissionsFailureStatus /// /// /// - /// + /// The status indicating why the ownership change failed public delegate void OnOwnershipPermissionsFailureDelegateHandler(OwnershipPermissionsFailureStatus changeOwnershipFailure); /// @@ -705,8 +705,8 @@ public OwnershipRequestStatus RequestOwnership() /// /// The delegate handler declaration used by . /// - /// - /// + /// The ClientId of the client requesting ownership + /// True to approve the ownership request, false to deny the request and prevent ownership transfer public delegate bool OnOwnershipRequestedDelegateHandler(ulong clientRequesting); /// @@ -722,7 +722,6 @@ public OwnershipRequestStatus RequestOwnership() /// Invoked by ChangeOwnershipMessage /// /// the client requesting ownership - /// internal void OwnershipRequest(ulong clientRequestingOwnership) { var response = OwnershipRequestResponseStatus.Approved; @@ -811,7 +810,7 @@ public enum OwnershipRequestResponseStatus /// /// The delegate handler declaration used by . /// - /// + /// The status indicating whether the ownership request was approved or the reason for denial public delegate void OnOwnershipRequestResponseDelegateHandler(OwnershipRequestResponseStatus ownershipRequestResponse); /// @@ -2027,7 +2026,7 @@ internal bool TryRemoveParentCachedWorldPositionStays() /// This is a more convenient way to remove the parent without having to cast the null value to either or /// /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// + /// True if the parent was successfully removed, false if the operation failed or the object was already parentless public bool TryRemoveParent(bool worldPositionStays = true) { return TrySetParent((NetworkObject)null, worldPositionStays); diff --git a/com.unity.netcode.gameobjects/Runtime/Exceptions/InvalidParentException.cs b/com.unity.netcode.gameobjects/Runtime/Exceptions/InvalidParentException.cs index c7ecbaa4d4..1be7048208 100644 --- a/com.unity.netcode.gameobjects/Runtime/Exceptions/InvalidParentException.cs +++ b/com.unity.netcode.gameobjects/Runtime/Exceptions/InvalidParentException.cs @@ -12,13 +12,13 @@ public class InvalidParentException : Exception /// public InvalidParentException() { } - /// - /// + /// + /// The message that describes the invalid parent operation public InvalidParentException(string message) : base(message) { } - /// - /// - /// + /// + /// The message that describes the invalid parent operation + /// The exception that caused the current exception public InvalidParentException(string message, Exception innerException) : base(message, innerException) { } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs index 6b26e5306f..a102f3666e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs @@ -19,12 +19,12 @@ namespace Unity.Netcode ///
/// The parameter is indicated by index (and is 0-indexed); for example: ///
- /// + /// /// [SerializesGenericParameter(1)] /// public class MyClass<TTypeOne, TTypeTwo> /// { /// } - /// + /// ///
/// This tells the code generation for to generate /// serialized code for TTypeTwo (generic parameter 1). @@ -38,32 +38,32 @@ namespace Unity.Netcode ///
/// This attribute is properly inherited by subclasses. For example: ///
- /// + /// /// [SerializesGenericParameter(0)] /// public class MyClass<T> /// { /// } - ///
+ /// /// public class MySubclass1 : MyClass<Foo> /// { /// } - ///
+ /// /// public class MySubclass2<T> : MyClass<T> /// { /// } - ///
+ /// /// [SerializesGenericParameter(1)] /// public class MySubclass3<TTypeOne, TTypeTwo> : MyClass<TTypeOne> /// { /// } - ///
+ /// /// public class MyBehaviour : NetworkBehaviour /// { /// public MySubclass1 TheValue; /// public MySubclass2<Bar> TheValue; /// public MySubclass3<Baz, Qux> TheValue; /// } - ///
+ /// ///
/// The above code will trigger generation of serialization code for Foo (passed directly to the /// base class), Bar (passed indirectly to the base class), Baz (passed indirectly to the base class), diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs index 3f9d4f9d88..9d81bdecf9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -2,8 +2,14 @@ namespace Unity.Netcode { + /// + /// The base abstract RPC Target class used by all universal RPC targets. + /// public abstract class BaseRpcTarget : IDisposable { + /// + /// The instance which can be used to handle sending and receiving the specific target(s) + /// protected NetworkManager m_NetworkManager; private bool m_Locked; @@ -22,6 +28,10 @@ internal BaseRpcTarget(NetworkManager manager) m_NetworkManager = manager; } + /// + /// Can be used to provide additional lock checks before disposing the target. + /// + /// The exception thrown if the target is still locked when disposed. protected void CheckLockBeforeDispose() { if (m_Locked) @@ -30,6 +40,9 @@ protected void CheckLockBeforeDispose() } } + /// + /// Invoked when the target is disposed. + /// public abstract void Dispose(); internal abstract void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index 2864205769..7b651ebcd4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -144,6 +144,9 @@ internal RpcTarget(NetworkManager manager) m_CachedProxyRpcTarget.Lock(); } + /// + /// Invoked when this class is disposed. + /// public void Dispose() { Everyone.Dispose(); @@ -245,11 +248,11 @@ public void Dispose() /// /// Send to a specific single client ID. /// - /// + /// The ID of the client to send to /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Single(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to the specified client. The type of target (cached or new instance) depends on the parameter public BaseRpcTarget Single(ulong clientId, RpcTargetUse use) { if (clientId == m_NetworkManager.LocalClientId) @@ -278,11 +281,11 @@ public BaseRpcTarget Single(ulong clientId, RpcTargetUse use) /// /// Send to everyone EXCEPT a specific single client ID. /// - /// + /// The IDs of the client to exclude from receiving the message /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Not(ulong excludedClientId, RpcTargetUse use) { IGroupRpcTarget target; @@ -291,7 +294,7 @@ public BaseRpcTarget Not(ulong excludedClientId, RpcTargetUse use) if (use == RpcTargetUse.Persistent) { target = new RpcTargetGroup(m_NetworkManager); - } + } // HELLO from the dark side else { target = m_CachedTargetGroup; @@ -331,11 +334,11 @@ public BaseRpcTarget Not(ulong excludedClientId, RpcTargetUse use) /// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient /// Group method if the group list is dynamically constructed. ///
- /// + /// The IDs of the client that should receive the message. /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Group(NativeArray clientIds, RpcTargetUse use) { IGroupRpcTarget target; @@ -375,11 +378,11 @@ public BaseRpcTarget Group(NativeArray clientIds, RpcTargetUse use) /// NativeList can be trivially constructed using Allocator.Temp, making this an efficient /// Group method if the group list is dynamically constructed. ///
- /// + /// The IDs of the client that should receive the message. /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Group(NativeList clientIds, RpcTargetUse use) { var asArray = clientIds.AsArray(); @@ -392,11 +395,11 @@ public BaseRpcTarget Group(NativeList clientIds, RpcTargetUse use) /// if you either have no strict performance requirements, or have the group of client IDs cached so /// it is not created each time. ///
- /// + /// The IDs of the client that should receive the message. /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Group(ulong[] clientIds, RpcTargetUse use) { return Group(new NativeArray(clientIds, Allocator.Temp), use); @@ -409,11 +412,12 @@ public BaseRpcTarget Group(ulong[] clientIds, RpcTargetUse use) /// This override is only recommended if you either have no strict performance requirements, /// or have the group of client IDs cached so it is not created each time. ///
- /// + /// The type + /// The IDs of the client that should receive the message. /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Group(T clientIds, RpcTargetUse use) where T : IEnumerable { IGroupRpcTarget target; @@ -453,11 +457,11 @@ public BaseRpcTarget Group(T clientIds, RpcTargetUse use) where T : IEnumerab /// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient /// Group method if the group list is dynamically constructed. ///
- /// + /// The IDs of the client to exclude from receiving the message /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Not(NativeArray excludedClientIds, RpcTargetUse use) { IGroupRpcTarget target; @@ -513,11 +517,11 @@ public BaseRpcTarget Not(NativeArray excludedClientIds, RpcTargetUse use) /// NativeList can be trivially constructed using Allocator.Temp, making this an efficient /// Group method if the group list is dynamically constructed. ///
- /// + /// The IDs of the client to exclude from receiving the message /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Not(NativeList excludedClientIds, RpcTargetUse use) { var asArray = excludedClientIds.AsArray(); @@ -530,11 +534,11 @@ public BaseRpcTarget Not(NativeList excludedClientIds, RpcTargetUse use) /// if you either have no strict performance requirements, or have the group of client IDs cached so /// it is not created each time. ///
- /// + /// The IDs of the client to exclude from receiving the message /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Not(ulong[] excludedClientIds, RpcTargetUse use) { return Not(new NativeArray(excludedClientIds, Allocator.Temp), use); @@ -547,11 +551,12 @@ public BaseRpcTarget Not(ulong[] excludedClientIds, RpcTargetUse use) /// This override is only recommended if you either have no strict performance requirements, /// or have the group of client IDs cached so it is not created each time. ///
- /// + /// The type + /// The IDs of the client to exclude from receiving the message /// will return a cached target, which should not be stored as it will /// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.

will /// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them. - /// + /// A configured to send to all clients except the specified one. The type of target (cached or new instance) depends on the parameter and whether the caller is the server public BaseRpcTarget Not(T excludedClientIds, RpcTargetUse use) where T : IEnumerable { IGroupRpcTarget target; diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index 2a08293206..d9d4e87393 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -20,24 +20,24 @@ public enum StaleDataHandling /// /// /// - /// Snap: In this mode (with set to + /// Snap: In this mode (with set to /// and no callback), /// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value, - /// resulting in a "snap" to the new value if it is different from the anticipated value. + /// resulting in a "snap" to the new value if it is different from the anticipated value. /// - /// Smooth: In this mode (with set to + /// Smooth: In this mode (with set to /// and an callback that calls /// from the anticipated value to the authority value with an appropriate /// -style smooth function), when a more up-to-date value is received from the authority, - /// it will interpolate over time from an incorrect anticipated value to the correct authoritative value. + /// it will interpolate over time from an incorrect anticipated value to the correct authoritative value. /// - /// Constant Reanticipation: In this mode (with set to + /// Constant Reanticipation: In this mode (with set to /// and an that calculates a /// new anticipated value based on the current authoritative value), when a more up-to-date value is received from /// the authority, user code calculates a new anticipated value, possibly calling to interpolate /// between the previous anticipation and the new anticipation. This is useful for values that change frequently and /// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply - /// need a one-time anticipation when the user performs that action. + /// need a one-time anticipation when the user performs that action. /// /// /// @@ -177,7 +177,7 @@ public bool ShouldReanticipate /// Sets the current value of the variable on the expectation that the authority will set the variable /// to the same value within one network round trip (i.e., in response to an RPC). ///
- /// + /// The anticipated value that is expected to be confirmed by the authority public void Anticipate(T value) { if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress || !m_NetworkBehaviour.NetworkManager.IsListening) @@ -334,10 +334,10 @@ private void OnValueChangedInternal(T previousValue, T newValue) /// Interpolate this variable from to over of /// real time. The duration uses , so it is affected by . ///
- /// - /// - /// - /// + /// The starting value for the interpolation + /// The target value to interpolate towards + /// The duration of the interpolation in seconds + /// A delegate that defines how the interpolation should be performed between the two values. It provides a function to interpolate between two values based on a percentage. public void Smooth(in T from, in T to, float durationSeconds, SmoothDelegate how) { if (durationSeconds <= 0) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 4a3499730f..53b7f4f8fb 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -30,10 +30,10 @@ public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatabl ///
public NetworkList() { } - /// - /// - /// - /// + /// + /// An optional collection of initial values to populate the list. If null, the list will start empty. + /// The read permission level for the network list. Determines who can read the list (e.g., server-only or everyone). Default is defined by DefaultReadPerm + /// The write permission level for the network list. Determines who can modify the list (e.g., server-only or specific clients). Default is defined by DefaultWritePerm. public NetworkList(IEnumerable values = default, NetworkVariableReadPermission readPerm = DefaultReadPerm, NetworkVariableWritePermission writePerm = DefaultWritePerm) @@ -54,7 +54,7 @@ public NetworkList(IEnumerable values = default, Dispose(); } - /// + /// public override void ResetDirty() { base.ResetDirty(); @@ -64,7 +64,7 @@ public override void ResetDirty() } } - /// + /// public override bool IsDirty() { // we call the base class to allow the SetDirty() mechanism to work @@ -76,7 +76,7 @@ internal void MarkNetworkObjectDirty() MarkNetworkBehaviourDirty(); } - /// + /// public override void WriteDelta(FastBufferWriter writer) { @@ -132,7 +132,7 @@ public override void WriteDelta(FastBufferWriter writer) } } - /// + /// public override void WriteField(FastBufferWriter writer) { writer.WriteValueSafe((ushort)m_List.Length); @@ -142,7 +142,7 @@ public override void WriteField(FastBufferWriter writer) } } - /// + /// public override void ReadField(FastBufferReader reader) { m_List.Clear(); @@ -155,7 +155,7 @@ public override void ReadField(FastBufferReader reader) } } - /// + /// public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { /// This is only invoked by and the only time @@ -394,7 +394,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) } } - /// + /// /// /// For NetworkList, we just need to reset dirty if a server has read deltas /// @@ -406,13 +406,13 @@ internal override void PostDeltaRead() } } - /// + /// public IEnumerator GetEnumerator() { return m_List.GetEnumerator(); } - /// + /// public void Add(T item) { // check write permissions @@ -434,7 +434,7 @@ public void Add(T item) HandleAddListEvent(listEvent); } - /// + /// public void Clear() { // check write permissions @@ -454,14 +454,14 @@ public void Clear() HandleAddListEvent(listEvent); } - /// + /// public bool Contains(T item) { int index = m_List.IndexOf(item); return index != -1; } - /// + /// public bool Remove(T item) { // check write permissions @@ -488,16 +488,16 @@ public bool Remove(T item) return true; } - /// + /// public int Count => m_List.Length; - /// + /// public int IndexOf(T item) { return m_List.IndexOf(item); } - /// + /// public void Insert(int index, T item) { // check write permissions @@ -527,7 +527,7 @@ public void Insert(int index, T item) HandleAddListEvent(listEvent); } - /// + /// public void RemoveAt(int index) { // check write permissions @@ -551,7 +551,7 @@ public void RemoveAt(int index) - /// + /// public T this[int index] { get => m_List[index]; diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index c712a90223..4740abd391 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -135,11 +135,11 @@ public virtual T Value /// Invoke this method to check if a collection's items are dirty. /// The default behavior is to exit early if the is already dirty. ///
- /// when true, this check will force a full item collection check even if the NetworkVariable is already dirty /// /// This is to be used as a way to check if a containing a managed collection has any changees to the collection items.
- /// If you invoked this when a collection is dirty, it will not trigger the unless you set to true.
+ /// If you invoked this when a collection is dirty, it will not trigger the unless you set forceCheck param to true.
///
+ /// when true, this check will force a full item collection check even if the NetworkVariable is already dirty public bool CheckDirtyState(bool forceCheck = false) { var isDirty = base.IsDirty(); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 66a5b8f955..c398e2f87b 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -148,7 +148,7 @@ public virtual void OnInitialize() /// /// Sets the update traits for this network variable to determine how frequently it will send updates. /// - /// + /// The new update traits to apply to this network variable public void SetUpdateTraits(NetworkVariableUpdateTraits traits) { UpdateTraits = traits; @@ -159,7 +159,7 @@ public void SetUpdateTraits(NetworkVariableUpdateTraits traits) /// If not, no update will be sent even if the variable is dirty, unless the time since last update exceeds /// the ' . ///
- /// + /// True if the variable exceeds the dirtiness threshold or the time since the last update exceeds MaxSecondsBetweenUpdates. otherwise, false public virtual bool ExceedsDirtinessThreshold() { return true; diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs index 8401df81af..b2edd738a3 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -32,6 +32,7 @@ public static class NetworkVariableSerialization /// generic, it is better to check their equality yourself. ///
public static EqualsDelegate AreEqual { get; internal set; } + /// /// Serialize a value using the best-known serialization method for a generic value. /// Will reliably serialize any value that is passed to it correctly with no boxing. @@ -47,8 +48,8 @@ public static class NetworkVariableSerialization /// . is called, which, by default, /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. /// - /// - /// + /// The FastBufferWriter to write the serialized data to + /// Reference to the value to serialize public static void Write(FastBufferWriter writer, ref T value) { Serializer.Write(writer, ref value); @@ -72,8 +73,8 @@ public static void Write(FastBufferWriter writer, ref T value) /// . is called, which, by default, /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. ///
- /// - /// + /// The FastBufferReader to read the serialized data from + /// Reference to store the deserialized value public static void Read(FastBufferReader reader, ref T value) { Serializer.Read(reader, ref value); @@ -94,8 +95,9 @@ public static void Read(FastBufferReader reader, ref T value) /// . is called, which, by default, /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. ///
- /// - /// + /// The FastBufferWriter to write the serialized delta to + /// Reference to the current value + /// Reference to the previous value for delta comparison public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) { Serializer.WriteDelta(writer, ref value, ref previousValue); @@ -119,8 +121,8 @@ public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previo /// . is called, which, by default, /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. ///
- /// - /// + /// The FastBufferReader to read the serialized delta from + /// Reference to update with the deserialized delta public static void ReadDelta(FastBufferReader reader, ref T value) { Serializer.ReadDelta(reader, ref value); @@ -143,8 +145,8 @@ public static void ReadDelta(FastBufferReader reader, ref T value) /// . is called, which, by default, /// will throw an exception, unless you have assigned a user duplication callback to it at runtime. ///
- /// - /// + /// The source value to duplicate + /// Reference to store the duplicated value public static void Duplicate(in T value, ref T duplicatedValue) { Serializer.Duplicate(value, ref duplicatedValue); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs index bbe48ab16f..8a639258da 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedILPPInitializers.cs @@ -43,7 +43,7 @@ internal static void InitializeIntegerSerialization() /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// - /// + /// The unmanaged type to be registered for serialization public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanaged { NetworkVariableSerialization.Serializer = new UnmanagedTypeSerializer(); @@ -52,7 +52,7 @@ public static void InitializeSerializer_UnmanagedByMemcpy() where T : unmanag /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// - /// + /// The unmanaged element type of the array to be registered for serialization public static void InitializeSerializer_UnmanagedByMemcpyArray() where T : unmanaged { NetworkVariableSerialization>.Serializer = new UnmanagedArraySerializer(); @@ -62,7 +62,7 @@ public static void InitializeSerializer_UnmanagedByMemcpyArray() where T : un /// /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// - /// + /// The unmanaged element type contained in the NativeList public static void InitializeSerializer_UnmanagedByMemcpyList() where T : unmanaged { NetworkVariableSerialization>.Serializer = new UnmanagedListSerializer(); @@ -71,7 +71,7 @@ public static void InitializeSerializer_UnmanagedByMemcpyList() where T : unm /// /// Registeres a native hash set (this generic implementation works with all types) /// - /// + /// The unmanaged element type contained in the NativeHashSet, must implement IEquatable public static void InitializeSerializer_NativeHashSet() where T : unmanaged, IEquatable { NetworkVariableSerialization>.Serializer = new NativeHashSetSerializer(); @@ -80,7 +80,8 @@ public static void InitializeSerializer_NativeHashSet() where T : unmanaged, /// /// Registeres a native hash set (this generic implementation works with all types) /// - /// + /// The unmanaged key type, must implement IEquatable + /// The unmanaged value type stored in the hash map public static void InitializeSerializer_NativeHashMap() where TKey : unmanaged, IEquatable where TVal : unmanaged @@ -92,7 +93,7 @@ public static void InitializeSerializer_NativeHashMap() /// /// Registeres a native hash set (this generic implementation works with all types) /// - /// + /// The type of elements contained in the List public static void InitializeSerializer_List() { NetworkVariableSerialization>.Serializer = new ListSerializer(); @@ -101,7 +102,7 @@ public static void InitializeSerializer_List() /// /// Registeres a native hash set (this generic implementation works with all types) /// - /// + /// The type of elements contained in the HashSet, must implement IEquatable public static void InitializeSerializer_HashSet() where T : IEquatable { NetworkVariableSerialization>.Serializer = new HashSetSerializer(); @@ -110,7 +111,8 @@ public static void InitializeSerializer_HashSet() where T : IEquatable /// /// Registeres a native hash set (this generic implementation works with all types) /// - /// + /// The type of keys in the Dictionary, must implement IEquatable + /// The type of values stored in the Dictionary public static void InitializeSerializer_Dictionary() where TKey : IEquatable { NetworkVariableSerialization>.Serializer = new DictionarySerializer(); @@ -120,7 +122,7 @@ public static void InitializeSerializer_Dictionary() where TKey : IE /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize ///
- /// + /// The unmanaged type that implements INetworkSerializable public static void InitializeSerializer_UnmanagedINetworkSerializable() where T : unmanaged, INetworkSerializable { NetworkVariableSerialization.Serializer = new UnmanagedNetworkSerializableSerializer(); @@ -130,7 +132,7 @@ public static void InitializeSerializer_UnmanagedINetworkSerializable() where /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize ///
- /// + /// The unmanaged element type that implements INetworkSerializable public static void InitializeSerializer_UnmanagedINetworkSerializableArray() where T : unmanaged, INetworkSerializable { NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableArraySerializer(); @@ -141,7 +143,7 @@ public static void InitializeSerializer_UnmanagedINetworkSerializableArray() /// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize ///
- /// + /// The unmanaged element type that implements INetworkSerializable public static void InitializeSerializer_UnmanagedINetworkSerializableList() where T : unmanaged, INetworkSerializable { NetworkVariableSerialization>.Serializer = new UnmanagedNetworkSerializableListSerializer(); @@ -152,7 +154,7 @@ public static void InitializeSerializer_UnmanagedINetworkSerializableList() w /// Registers a managed type that implements INetworkSerializable and will be serialized through a call to /// NetworkSerialize ///
- /// + /// The managed type that implements INetworkSerializable and has a parameterless constructor public static void InitializeSerializer_ManagedINetworkSerializable() where T : class, INetworkSerializable, new() { NetworkVariableSerialization.Serializer = new ManagedNetworkSerializableSerializer(); @@ -162,7 +164,7 @@ public static void InitializeSerializer_UnmanagedINetworkSerializableList() w /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString /// serializers ///
- /// + /// The FixedString type that implements INativeList{byte} and IUTF8Bytes public static void InitializeSerializer_FixedString() where T : unmanaged, INativeList, IUTF8Bytes { NetworkVariableSerialization.Serializer = new FixedStringSerializer(); @@ -172,7 +174,7 @@ public static void InitializeSerializer_FixedString() where T : unmanaged, IN /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString /// serializers ///
- /// + /// The FixedString type that implements INativeList{byte} and IUTF8Bytes public static void InitializeSerializer_FixedStringArray() where T : unmanaged, INativeList, IUTF8Bytes { NetworkVariableSerialization>.Serializer = new FixedStringArraySerializer(); @@ -183,7 +185,7 @@ public static void InitializeSerializer_FixedStringArray() where T : unmanage /// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString /// serializers ///
- /// + /// The FixedString type that implements INativeList{byte} and IUTF8Bytes public static void InitializeSerializer_FixedStringList() where T : unmanaged, INativeList, IUTF8Bytes { NetworkVariableSerialization>.Serializer = new FixedStringListSerializer(); @@ -193,7 +195,7 @@ public static void InitializeSerializer_FixedStringList() where T : unmanaged /// /// Registers a managed type that will be checked for equality using T.Equals() /// - /// + /// The managed type that implements IEquatable public static void InitializeEqualityChecker_ManagedIEquatable() where T : class, IEquatable { NetworkVariableSerialization.AreEqual = NetworkVariableEquality.EqualityEqualsObject; @@ -202,7 +204,7 @@ public static void InitializeEqualityChecker_ManagedIEquatable() where T : cl /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// - /// + /// The unmanaged type to register public static void InitializeEqualityChecker_UnmanagedIEquatable() where T : unmanaged, IEquatable { NetworkVariableSerialization.AreEqual = NetworkVariableEquality.EqualityEquals; @@ -211,15 +213,16 @@ public static void InitializeEqualityChecker_UnmanagedIEquatable() where T : /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// - /// + /// The unmanaged element type that implements IEquatable public static void InitializeEqualityChecker_UnmanagedIEquatableArray() where T : unmanaged, IEquatable { NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsArray; } + /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// - /// + /// The type of elements in the List public static void InitializeEqualityChecker_List() { NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsList; @@ -227,7 +230,7 @@ public static void InitializeEqualityChecker_List() /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// - /// + /// The type of elements in the HashSet public static void InitializeEqualityChecker_HashSet() where T : IEquatable { NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsHashSet; @@ -235,7 +238,8 @@ public static void InitializeEqualityChecker_HashSet() where T : IEquatable /// Registers an unmanaged type that will be checked for equality using T.Equals() ///
- /// + /// The type of keys in the Dictionary, must implement IEquatable + /// The type of values in the Dictionary public static void InitializeEqualityChecker_Dictionary() where TKey : IEquatable { @@ -246,7 +250,7 @@ public static void InitializeEqualityChecker_Dictionary() /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// - /// + /// The unmanaged element type that implements IEquatable public static void InitializeEqualityChecker_UnmanagedIEquatableList() where T : unmanaged, IEquatable { NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsNativeList; @@ -254,7 +258,7 @@ public static void InitializeEqualityChecker_UnmanagedIEquatableList() where /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// - /// + /// The unmanaged element type that implements IEquatable public static void InitializeEqualityChecker_NativeHashSet() where T : unmanaged, IEquatable { NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.EqualityEqualsNativeHashSet; @@ -262,7 +266,8 @@ public static void InitializeEqualityChecker_NativeHashSet() where T : unmana /// /// Registers an unmanaged type that will be checked for equality using T.Equals() /// - /// + /// The unmanaged key type that implements IEquatable + /// The unmanaged value type public static void InitializeEqualityChecker_NativeHashMap() where TKey : unmanaged, IEquatable where TVal : unmanaged @@ -275,7 +280,7 @@ public static void InitializeEqualityChecker_NativeHashMap() /// Registers an unmanaged type that will be checked for equality using memcmp and only considered /// equal if they are bitwise equivalent in memory ///
- /// + /// The unmanaged type to register for equality checking public static void InitializeEqualityChecker_UnmanagedValueEquals() where T : unmanaged { NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ValueEquals; @@ -285,7 +290,7 @@ public static void InitializeEqualityChecker_UnmanagedValueEquals() where T : /// Registers an unmanaged type that will be checked for equality using memcmp and only considered /// equal if they are bitwise equivalent in memory ///
- /// + /// The unmanaged element type to register for equality checking public static void InitializeEqualityChecker_UnmanagedValueEqualsArray() where T : unmanaged { NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.ValueEqualsArray; @@ -296,7 +301,7 @@ public static void InitializeEqualityChecker_UnmanagedValueEqualsArray() wher /// Registers an unmanaged type that will be checked for equality using memcmp and only considered /// equal if they are bitwise equivalent in memory ///
- /// + /// The unmanaged element type to register for equality checking public static void InitializeEqualityChecker_UnmanagedValueEqualsList() where T : unmanaged { NetworkVariableSerialization>.AreEqual = NetworkVariableEquality.ValueEqualsList; @@ -306,7 +311,7 @@ public static void InitializeEqualityChecker_UnmanagedValueEqualsList() where /// /// Registers a managed type that will be checked for equality using the == operator /// - /// + /// The class type to register for equality checking public static void InitializeEqualityChecker_ManagedClassEquals() where T : class { NetworkVariableSerialization.AreEqual = NetworkVariableEquality.ClassEquals; diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs index c39ca0f9a2..a85d4fcaf6 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode /// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows /// users to tell NetworkVariable about those extension methods (or simply pass in a lambda) ///
- /// + /// The type of value being serialized public class UserNetworkVariableSerialization { /// @@ -40,7 +40,6 @@ public class UserNetworkVariableSerialization /// /// The read value delegate handler definition /// - /// The to read the value of type `T` /// The value of type `T` to be read public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 8996dfcfd7..1d87687e6d 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -15,7 +15,7 @@ namespace Unity.Netcode /// Note: This is only when is enabled.
/// *** Do not start new scene events within scene event notification callbacks.
/// See also:
- /// + /// ///
public class SceneEvent { @@ -146,10 +146,10 @@ public class NetworkSceneManager : IDisposable /// /// The delegate callback definition for scene event notifications.
/// See also:
- ///
- /// + ///
+ /// ///
- /// + /// SceneEvent which contains information about the scene event, including type, progress, and scene details public delegate void SceneEventDelegate(SceneEvent sceneEvent); /// @@ -175,7 +175,7 @@ public class NetworkSceneManager : IDisposable /// /// Delegate declaration for the OnLoad event.
/// See also:
- /// for more information + /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// name of the scene being processed @@ -186,7 +186,7 @@ public class NetworkSceneManager : IDisposable /// /// Delegate declaration for the OnUnload event.
/// See also:
- /// for more information + /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// name of the scene being processed @@ -196,7 +196,7 @@ public class NetworkSceneManager : IDisposable /// /// Delegate declaration for the OnSynchronize event.
/// See also:
- /// for more information + /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) public delegate void OnSynchronizeDelegateHandler(ulong clientId); @@ -204,8 +204,8 @@ public class NetworkSceneManager : IDisposable /// /// Delegate declaration for the OnLoadEventCompleted and OnUnloadEventCompleted events.
/// See also:
- ///
- /// + ///
+ /// ///
/// scene pertaining to this event /// of the associated event completed @@ -216,7 +216,7 @@ public class NetworkSceneManager : IDisposable /// /// Delegate declaration for the OnLoadComplete event.
/// See also:
- /// for more information + /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// the scene name pertaining to this event @@ -226,7 +226,7 @@ public class NetworkSceneManager : IDisposable /// /// Delegate declaration for the OnUnloadComplete event.
/// See also:
- /// for more information + /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// the scene name pertaining to this event @@ -235,7 +235,7 @@ public class NetworkSceneManager : IDisposable /// /// Delegate declaration for the OnSynchronizeComplete event.
/// See also:
- /// for more information + /// for more information ///
/// the client that completed this event public delegate void OnSynchronizeCompleteDelegateHandler(ulong clientId); @@ -1261,7 +1261,7 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress /// Unloads an additively loaded scene. If you want to unload a mode loaded scene load another scene. /// When applicable, the is delivered within the via the ///
- /// + /// The Unity Scene to be unloaded /// ( means it was successful) public SceneEventProgressStatus UnloadScene(Scene scene) { @@ -3131,21 +3131,56 @@ private void ProcessDeferredCreateObjectMessages() DeferredObjectCreationList.Clear(); } + /// + /// The scene map type . + /// public enum MapTypes { + /// + /// Denotes the server to client scene map type. + /// ServerToClient, + /// + /// Denotes the client to server scene map type. + /// ClientToServer } + + /// + /// Provides the status of a loaded scene + /// public struct SceneMap : INetworkSerializable { + /// + /// The scene mapping type . + /// public MapTypes MapType; + /// + /// The struct of the scene mapped. + /// public Scene Scene; + /// + /// When true, the scene is present. + /// public bool ScenePresent; + /// + /// The name of the scene + /// public string SceneName; + /// + /// The scene's server handle (a.k.a network scene handle) + /// public int ServerHandle; + /// + /// The mapped handled. This could be the ServerHandle or LocalHandle depending upon context (client or server). + /// public int MappedLocalHandle; + /// + /// The local handle of the scene. + /// public int LocalHandle; + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref MapType); @@ -3165,6 +3200,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } + /// + /// Returns the list of all scene mappings when scene management is enabled. + /// + /// The map type to return + /// A list of objects representing the scene mappings. public List GetSceneMapping(MapTypes mapType) { var mapping = new List(); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 613c216328..7f03e08493 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode /// Used by for messages.
/// Note: This is only when is enabled.
/// See also:
- /// + /// ///
public enum SceneEventType : byte { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs index 85cc0909e0..faf4c960fa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BufferSerializer.cs @@ -301,7 +301,6 @@ public void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unus /// The network serializable type /// The values to read/write /// The allocator to use to construct the resulting NativeArray when reading - /// An unused parameter used for enabling overload resolution of FixedStrings public void SerializeValue(ref NativeArray value, Allocator allocator) where T : unmanaged, INativeList, IUTF8Bytes => m_Implementation.SerializeValue(ref value, allocator); @@ -311,7 +310,6 @@ public void SerializeValue(ref NativeArray value, Allocator allocator) ///
/// The network serializable type /// The values to read/write - /// An unused parameter used for enabling overload resolution of FixedStrings public void SerializeValue(ref NativeList value) where T : unmanaged, INativeList, IUTF8Bytes => m_Implementation.SerializeValue(ref value); #endif @@ -330,8 +328,8 @@ public void SerializeValue(ref NativeList value) /// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be /// noticeable if serializing a very large number of items. ///
- /// - /// + /// The number of values that will need to be read or written + /// True if there is sufficient space available for the specified amount, false otherwise public bool PreCheck(int amount) { return m_Implementation.PreCheck(amount); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs index 58b7ea1e33..b064f2dbce 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BytePacker.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode { /// /// Utility class for packing values in serialization. - /// to unpack packed values. + /// to unpack packed values. /// public static class BytePacker { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs index 2d2cd88716..2c84fff991 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ByteUnpacker.cs @@ -7,7 +7,7 @@ namespace Unity.Netcode /// /// Byte Unpacking Helper Class /// Use this class to unpack values during deserialization for values that were packed. - /// to pack unpacked values + /// to pack unpacked values /// public static class ByteUnpacker { @@ -282,7 +282,7 @@ public static void ReadValuePacked(FastBufferReader reader, out Quaternion rotat /// Reads a string in a packed format ///
/// The reader to read from - /// + /// string to read [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void ReadValuePacked(FastBufferReader reader, out string s) { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index 3612855553..bf30e04b4b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -8,9 +8,9 @@ namespace Unity.Netcode { /// /// Optimized class used for reading values from a byte stream - /// - /// - /// + /// + /// + /// /// public struct FastBufferReader : IDisposable { @@ -93,22 +93,22 @@ internal unsafe void CommitBitwiseReads(int amount) /// /// Create a FastBufferReader from a NativeArray. /// - /// A new buffer will be created using the given and the value will be copied in. + /// A new buffer will be created using the specified allocator and the value will be copied in. /// FastBufferReader will then own the data. /// - /// The exception to this is when the passed in is Allocator.None. In this scenario, + /// The exception to this is when the allocator passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp so it should be treated as if it's a ref struct and not allowed to outlive /// the context in which it was created (it should neither be returned from that function nor - /// stored anywhere in heap memory). This is true, unless the param is explicitly set + /// stored anywhere in heap memory). This is true, unless the internal allocator param is explicitly set /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// should manually call Dispose() when it is no longer needed. /// - /// + /// The NativeArray to create the reader from /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance - /// - /// + /// The number of bytes to read from the buffer. If set to -1, the entire buffer length will be used + /// The offset into the buffer to start reading from /// The allocator type used for internal data when this reader points directly at a buffer owned by someone else public unsafe FastBufferReader(NativeArray buffer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp) { @@ -168,15 +168,15 @@ public unsafe FastBufferReader(byte[] buffer, Allocator copyAllocator, int lengt /// /// Create a FastBufferReader from an existing byte buffer. /// - /// A new buffer will be created using the given and the value will be copied in. + /// A new buffer will be created using the given specified allocator and the value will be copied in. /// FastBufferReader will then own the data. /// - /// The exception to this is when the passed in is Allocator.None. In this scenario, + /// The exception to this is when the specified allocator passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// the context in which it was created (it should neither be returned from that function nor - /// stored anywhere in heap memory). This is true, unless the param is explicitly set + /// stored anywhere in heap memory). This is true, unless the internal allocator param is explicitly set /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// should manually call Dispose() when it is no longer needed. /// @@ -193,15 +193,15 @@ public unsafe FastBufferReader(byte* buffer, Allocator copyAllocator, int length /// /// Create a FastBufferReader from a FastBufferWriter. /// - /// A new buffer will be created using the given and the value will be copied in. + /// A new buffer will be created using the given specified allocator and the value will be copied in. /// FastBufferReader will then own the data. /// - /// The exception to this is when the passed in is Allocator.None. In this scenario, + /// The exception to this is when the specified allocator passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// the context in which it was created (it should neither be returned from that function nor - /// stored anywhere in heap memory). This is true, unless the param is explicitly set + /// stored anywhere in heap memory). This is true, unless the internal Allocator param is explicitly set /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// should manually call Dispose() when it is no longer needed. /// @@ -220,10 +220,10 @@ public unsafe FastBufferReader(FastBufferWriter writer, Allocator copyAllocator, /// want to change the copyAllocator that a reader is allocated to - for example, upgrading a Temp reader to /// a Persistent one to be processed later. /// - /// A new buffer will be created using the given and the value will be copied in. + /// A new buffer will be created using the given specified allocator and the value will be copied in. /// FastBufferReader will then own the data. /// - /// The exception to this is when the passed in is Allocator.None. In this scenario, + /// The exception to this is when the specified allocator passed in is Allocator.None. In this scenario, /// ownership of the data remains with the caller and the reader will point at it directly. /// When created with Allocator.None, FastBufferReader will allocate some internal data using /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive @@ -440,9 +440,9 @@ public unsafe byte[] ToArray() /// /// Read an INetworkSerializable /// - /// + /// The type that implements INetworkSerializable and can be deserialized /// INetworkSerializable instance - /// + /// Thrown if the type T does not properly implement NetworkSerialize public void ReadNetworkSerializable(out T value) where T : INetworkSerializable, new() { value = new T(); @@ -454,8 +454,8 @@ public unsafe byte[] ToArray() /// Read an array of INetworkSerializables ///
/// INetworkSerializable instance - /// the array to read the values of type `T` into - /// + /// the array to read the values of type `T` from + /// Thrown if the type T does not properly implement NetworkSerialize public void ReadNetworkSerializable(out T[] value) where T : INetworkSerializable, new() { ReadValueSafe(out int size); @@ -471,8 +471,8 @@ public unsafe byte[] ToArray() ///
/// INetworkSerializable instance /// The allocator to use to construct the resulting NativeArray - /// the array to read the values of type `T` into - /// + /// the array to read the values of type `T` from + /// Thrown if the type T does not properly implement NetworkSerialize public void ReadNetworkSerializable(out NativeArray value, Allocator allocator) where T : unmanaged, INetworkSerializable { ReadValueSafe(out int size); @@ -489,8 +489,8 @@ public void ReadNetworkSerializable(out NativeArray value, Allocator alloc /// Read a NativeList of INetworkSerializables ///
/// INetworkSerializable instance - /// the array to read the values of type `T` into - /// + /// the array to read the values of type `T` from + /// Thrown if the type T does not properly implement NetworkSerialize public void ReadNetworkSerializableInPlace(ref NativeList value) where T : unmanaged, INetworkSerializable { ReadValueSafe(out int size); @@ -506,9 +506,9 @@ public void ReadNetworkSerializableInPlace(ref NativeList value) where T : /// Read an INetworkSerializable in-place, without constructing a new one /// Note that this will NOT check for null before calling NetworkSerialize ///
- /// + /// The type that implements INetworkSerializable and can be deserialized /// INetworkSerializable instance - /// + /// hrown if the type T does not properly implement NetworkSerialize public void ReadNetworkSerializableInPlace(ref T value) where T : INetworkSerializable { var bufferSerializer = new BufferSerializer(new BufferSerializerReader(this)); @@ -623,11 +623,11 @@ private void ReadLength(out int length) /// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it. ///
/// Value to read - /// Number of bytes - /// Offset into the value to write the bytes - /// the type value to read the value into - /// - /// + /// The number of bytes to read from the buffer into the value + /// The offset in bytes from the start of the value where the read bytes will be written + /// The unmanaged type to read into. Must be unmanaged to allow direct memory access + /// Thrown when attempting to use BufferReader in bytewise mode while in a bitwise context + /// Thrown when attempting to read without first calling TryBeginRead() or when reading beyond the allowed read mark [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged { @@ -1646,7 +1646,6 @@ public unsafe void ReadValueSafeInPlace(ref T value, FastBufferWriter.ForFixe ///
/// the value to read /// The allocator to use to construct the resulting NativeArray - /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadValueSafe(out NativeArray value, Allocator allocator) @@ -1668,7 +1667,6 @@ public unsafe void ReadValueSafe(out NativeArray value, Allocator allocato /// for multiple reads at once by calling TryBeginRead. ///
/// the value to read - /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadValueSafeTemp(out NativeArray value) @@ -1712,7 +1710,6 @@ public void ReadValueSafe(out T[] value, FastBufferWriter.ForFixedStrings unu /// for multiple reads at once by calling TryBeginRead. ///
/// the value to read - /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadValueSafeInPlace(ref NativeList value) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index 66d0da5b17..addd102981 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -8,9 +8,9 @@ namespace Unity.Netcode { /// /// Optimized class used for writing values into a byte stream - /// - /// - /// + /// + /// + /// /// public struct FastBufferWriter : IDisposable { @@ -314,9 +314,9 @@ public unsafe bool TryBeginWriteValue(in T value) where T : unmanaged /// Internal version of TryBeginWrite. /// Differs from TryBeginWrite only in that it won't ever move the AllowedWriteMark backward. ///
- /// - /// - /// + /// The number of bytes to check for write availability + /// True if the specified number of bytes can be written, false if there isn't enough space + /// Thrown when attempting to use BufferWriter in bytewise mode while in a bitwise context [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginWriteInternal(int bytes) { @@ -356,7 +356,7 @@ public unsafe bool TryBeginWriteInternal(int bytes) /// Returns an array representation of the underlying byte buffer. /// !!Allocates a new array!! ///
- /// + /// A new byte array containing a copy of the buffer's contents. [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte[] ToArray() { @@ -395,7 +395,7 @@ internal unsafe ArraySegment ToTempByteArray() /// /// Gets a direct pointer to the underlying buffer /// - /// + /// An unsafe pointer to the start of the underlying buffer memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte* GetUnsafePtr() { @@ -405,7 +405,7 @@ internal unsafe ArraySegment ToTempByteArray() /// /// Gets a direct pointer to the underlying buffer at the current read position /// - /// + /// An unsafe pointer to the underlying buffer memory offset by the current position [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe byte* GetUnsafePtrAtCurrentPosition() { @@ -417,7 +417,7 @@ internal unsafe ArraySegment ToTempByteArray() ///
/// The string to write /// Whether or not to use one byte per character. This will only allow ASCII - /// + /// The total number of bytes required to write the string, including the length field [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetWriteSize(string s, bool oneByteChars = false) { @@ -428,7 +428,7 @@ public static int GetWriteSize(string s, bool oneByteChars = false) /// Write an INetworkSerializable ///
/// The value to write - /// + /// The type of the value public void WriteNetworkSerializable(in T value) where T : INetworkSerializable { var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(this)); @@ -438,10 +438,10 @@ public void WriteNetworkSerializable(in T value) where T : INetworkSerializab /// /// Write an array of INetworkSerializables /// - /// The value to write - /// - /// - /// + /// The array of objects to write + /// The number of elements to write. If set to -1, will write all elements from offset to end + /// The starting position in the array from which to begin writing + /// The type of the value public void WriteNetworkSerializable(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable { int sizeInTs = count != -1 ? count : array.Length - offset; @@ -455,10 +455,10 @@ public void WriteNetworkSerializable(T[] array, int count = -1, int offset = /// /// Write a NativeArray of INetworkSerializables /// - /// The value to write - /// - /// - /// + /// The NativeArray containing the values to write + /// The number of elements to write. If -1 (default), writes array.Length - offset elements + /// The starting position in the array. Defaults to 0 + /// The type of the value public void WriteNetworkSerializable(NativeArray array, int count = -1, int offset = 0) where T : unmanaged, INetworkSerializable { int sizeInTs = count != -1 ? count : array.Length - offset; @@ -473,10 +473,10 @@ public void WriteNetworkSerializable(NativeArray array, int count = -1, in /// /// Write a NativeList of INetworkSerializables /// - /// The value to write - /// - /// - /// + /// The NativeArray containing the values to write + /// The number of elements to write. If -1 (default), writes array.Length - offset elements + /// The starting position in the array. Defaults to 0/param> + /// The type of the value public void WriteNetworkSerializable(NativeList array, int count = -1, int offset = 0) where T : unmanaged, INetworkSerializable { int sizeInTs = count != -1 ? count : array.Length - offset; @@ -562,8 +562,8 @@ public unsafe void WriteValueSafe(string s, bool oneByteChars = false) /// The array to write /// The amount of elements to write /// Where in the array to start - /// - /// + /// The type of elements in the array, must be unmanaged + /// The total number of bytes required to write the array, including the length field [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetWriteSize(T[] array, int count = -1, int offset = 0) where T : unmanaged { @@ -578,8 +578,8 @@ public static unsafe int GetWriteSize(T[] array, int count = -1, int offset = /// The array to write /// The amount of elements to write /// Where in the array to start - /// - /// + /// The type of elements in the array, must be unmanaged + /// The total number of bytes required to write the array, including the length field [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetWriteSize(NativeArray array, int count = -1, int offset = 0) where T : unmanaged { @@ -595,8 +595,8 @@ public static unsafe int GetWriteSize(NativeArray array, int count = -1, i /// The array to write /// The amount of elements to write /// Where in the array to start - /// - /// + /// The type of elements in the array, must be unmanaged + /// The total number of bytes required to write the array, including the length field [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetWriteSize(NativeList array, int count = -1, int offset = 0) where T : unmanaged { @@ -612,9 +612,9 @@ public static unsafe int GetWriteSize(NativeList array, int count = -1, in /// Value to write /// Number of bytes /// Offset into the value to begin reading the bytes - /// - /// - /// + /// The type of elements in the array, must be unmanaged + /// Thrown when attempting to use BufferWriter in bytewise mode while in a bitwise context + /// Thrown when attempting to write without first calling TryBeginWrite() or when writing beyond the allowed write mark [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged { @@ -857,10 +857,10 @@ public unsafe void CopyFrom(FastBufferWriter other) /// The ForStructs value here makes this the lowest-priority overload so other versions /// will be prioritized over this if they match ///
- /// - /// - /// - /// + /// The unmanaged value to calculate the size for + /// Unused parameter for overload resolution + /// The type of the value, must be unmanaged + /// The size in bytes required to write the value [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetWriteSize(in T value, ForStructs unused = default) where T : unmanaged { @@ -870,9 +870,9 @@ public static unsafe int GetWriteSize(in T value, ForStructs unused = default /// /// Get the write size for a FixedString /// - /// - /// - /// + /// The FixedString value to calculate the size for + /// The specific FixedString type, must implement INativeList{byte} and IUTF8Bytes + /// The size in bytes required to write the value public static int GetWriteSize(in T value) where T : unmanaged, INativeList, IUTF8Bytes { @@ -882,9 +882,9 @@ public static int GetWriteSize(in T value) /// /// Get the write size for an array of FixedStrings /// - /// - /// - /// + /// The NativeArray of FixedStrings to calculate the size for + /// The specific FixedString type, must implement INativeList{byte} and IUTF8Bytes + /// The total size in bytes required to write all strings, including all length fields public static int GetWriteSize(in NativeArray value) where T : unmanaged, INativeList, IUTF8Bytes { @@ -901,9 +901,9 @@ public static int GetWriteSize(in NativeArray value) /// /// Get the write size for an array of FixedStrings /// - /// - /// - /// + /// The NativeList of FixedStrings to calculate the size for + /// The specific FixedString type, must implement INativeList{byte} and IUTF8Bytes + /// The total size in bytes required to write all strings, including all length fields public static int GetWriteSize(in NativeList value) where T : unmanaged, INativeList, IUTF8Bytes { @@ -920,8 +920,8 @@ public static int GetWriteSize(in NativeList value) /// /// Get the size required to write an unmanaged value of type T /// - /// - /// + /// The type to calculate the size for, must be unmanaged + /// The size in bytes required to write a value of type T public static unsafe int GetWriteSize() where T : unmanaged { return sizeof(T); @@ -1907,7 +1907,6 @@ public void WriteValueSafe(T[] value, ForFixedStrings unused = default) /// for multiple writes at once by calling TryBeginWrite. ///
/// the value to write - /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in NativeArray value) @@ -1931,7 +1930,6 @@ public void WriteValueSafe(in NativeArray value) /// for multiple writes at once by calling TryBeginWrite. ///
/// the value to write - /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValueSafe(in NativeList value) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs index 64cdf755a5..7282db3601 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode /// This is a wrapper that adds `INetworkSerializeByMemcpy` support to existing structs that the developer /// doesn't have the ability to modify (for example, external structs like `Guid`). ///
- /// + /// The type to be wrapped, must be unmanaged and implement IEquatable{T} public struct ForceNetworkSerializeByMemcpy : INetworkSerializeByMemcpy, IEquatable> where T : unmanaged, IEquatable { /// diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs index bdddec4924..2ec4f7b95d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/IReaderWriter.cs @@ -145,7 +145,6 @@ void SerializeValue(ref T value, FastBufferWriter.ForFixedStrings unused = de /// /// The value to read/write /// The allocator to use to construct the resulting NativeArray when reading - /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized void SerializeValue(ref NativeArray value, Allocator allocator) where T : unmanaged, INativeList, IUTF8Bytes; @@ -155,7 +154,6 @@ void SerializeValue(ref NativeArray value, Allocator allocator) /// Read or write a NativeList of FixedString values ///
/// The value to read/write - /// An unused parameter used for enabling overload resolution based on generic constraints /// The type being serialized void SerializeValue(ref NativeList value) where T : unmanaged, INativeList, IUTF8Bytes; @@ -295,8 +293,11 @@ void SerializeValue(ref NativeList value) /// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be /// noticeable if serializing a very large number of items. ///
- /// - /// + /// The number of bytes to check for availability in the buffer + /// + /// True if there is sufficient space for the specified amount of bytes. + /// False if there isn't enough space available. + /// bool PreCheck(int amount); /// diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs index 5d8f9c7b93..19cecefc48 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs @@ -18,7 +18,7 @@ public struct NetworkBehaviourReference : INetworkSerializable, IEquatable struct. /// /// The to reference. - /// + /// Thrown when the provided NetworkBehaviour does not have an associated NetworkObject. This can happen if the behaviour is not properly attached to a networked GameObject. public NetworkBehaviourReference(NetworkBehaviour networkBehaviour) { if (networkBehaviour == null) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs index 60dcf045d3..32ab0ed162 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs @@ -25,8 +25,8 @@ public ulong NetworkObjectId /// Creates a new instance of the struct. ///
/// The to reference. - /// - /// + /// Thrown when networkObject is null (creates a null reference instead of throwing) + /// Thrown when the NetworkObject is not spawned. NetworkObjectReference can only be created from spawned NetworkObjects public NetworkObjectReference(NetworkObject networkObject) { if (networkObject == null) @@ -47,8 +47,8 @@ public NetworkObjectReference(NetworkObject networkObject) /// Creates a new instance of the struct. ///
/// The GameObject from which the component will be referenced. - /// - /// + /// Thrown when networkObject is null (creates a null reference instead of throwing) + /// Thrown when the NetworkObject is not spawned. NetworkObjectReference can only be created from spawned NetworkObjects public NetworkObjectReference(GameObject gameObject) { if (gameObject == null) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index bb0afc4f3a..fe0dd270e9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -24,7 +24,7 @@ public interface INetworkPrefabInstanceHandler /// the owner for the to be instantiated /// the initial/default position for the to be instantiated /// the initial/default rotation for the to be instantiated - /// + /// The instantiated NetworkObject instance. Returns null if instantiation fails. NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation); /// @@ -81,7 +81,7 @@ public bool AddHandler(GameObject networkPrefabAsset, INetworkPrefabInstanceHand /// /// the of the network prefab asset to be overridden /// the class that implements the interface to be registered - /// + /// true (registered) false (failed to register) public bool AddHandler(NetworkObject prefabAssetNetworkObject, INetworkPrefabInstanceHandler instanceHandler) { return AddHandler(prefabAssetNetworkObject.GlobalObjectIdHash, instanceHandler); @@ -92,7 +92,7 @@ public bool AddHandler(NetworkObject prefabAssetNetworkObject, INetworkPrefabIns ///
/// the value of the network prefab asset being overridden /// a class that implements the interface - /// + /// true (registered) false (failed to register) public bool AddHandler(uint globalObjectIdHash, INetworkPrefabInstanceHandler instanceHandler) { if (!m_PrefabAssetToPrefabHandler.ContainsKey(globalObjectIdHash)) @@ -343,8 +343,8 @@ public GameObject GetNetworkPrefabOverride(GameObject gameObject) /// should not be relied on and code shouldn't be written around it - your code should be written so that /// the asset is expected to be loaded before it's needed. ///
- /// - /// + /// The GameObject with NetworkObject component to add as a network prefab + /// Thrown when adding prefabs after startup with ForceSamePrefabs is enabled or prefab doesn't have a NetworkObject component public void AddNetworkPrefab(GameObject prefab) { if (m_NetworkManager.IsListening && m_NetworkManager.NetworkConfig.ForceSamePrefabs) @@ -375,7 +375,8 @@ public void AddNetworkPrefab(GameObject prefab) /// Like AddNetworkPrefab, when NetworkConfig.ForceSamePrefabs is enabled, /// this cannot be called after connecting. /// - /// + /// The GameObject to remove from the network prefab list + /// Thrown when attempting to remove prefabs after startup with ForceSamePrefabs enabled public void RemoveNetworkPrefab(GameObject prefab) { if (m_NetworkManager.IsListening && m_NetworkManager.NetworkConfig.ForceSamePrefabs) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 72265a17c3..4807e3dfc0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -54,6 +54,10 @@ public class NetworkSpawnManager private Dictionary> m_PlayerObjectsTable = new Dictionary>(); + /// + /// Returns the connect client identifiers + /// + /// connected client identifier list public List GetConnectedPlayers() { return m_PlayerObjectsTable.Keys.ToList(); @@ -402,6 +406,11 @@ private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient return false; } + /// + /// Not used. + /// + /// not used + /// not used protected virtual void InternalOnOwnershipChanged(ulong perviousOwner, ulong newOwner) { diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 7352c8e80f..916a35e6c4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -114,10 +114,11 @@ public NetworkTime ToFixedTime() } /// - /// Returns the time a number of ticks in the past. + /// Calculates a NetworkTime value representing a point in the past relative to the current time (few ticks in the past) /// - /// The number of ticks ago we're querying the time - /// + /// The number of ticks ago we're querying the time. + /// Optional parameter to specify a tick offset (fractional) value. + /// A NetworkTime value representing the calculated past time point public NetworkTime TimeTicksAgo(int ticks, float offset = 0.0f) { return this - new NetworkTime(TickRate, ticks, offset); diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 41327dbcd2..a40c88ed0a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -200,7 +200,7 @@ public static NetworkTimeSystem ServerTimeSystem() /// Advances the time system by a certain amount of time. Should be called once per frame with Time.unscaledDeltaTime or similar. /// /// The amount of time to advance. The delta time which passed since Advance was last called. - /// + /// True if a hard reset of the time system occurred due to large time offset differences. False if normal time advancement occurred public bool Advance(double deltaTimeSec) { m_TimeSec += deltaTimeSec; diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs index d647aeab17..e39dbc8dfc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode /// /// The generic transport class all Netcode for GameObjects network transport implementations /// derive from. Use this class to add a custom transport. - /// for an example of how a transport is integrated + /// for an example of how a transport is integrated /// public abstract class NetworkTransport : MonoBehaviour { @@ -104,13 +104,14 @@ protected void InvokeOnTransportEvent(NetworkEvent eventType, ulong clientId, Ar /// /// Initializes the transport /// - /// /// optionally pass in NetworkManager + /// optionally pass in NetworkManager public abstract void Initialize(NetworkManager networkManager = null); /// /// Invoked by NetworkManager at the beginning of its EarlyUpdate. /// For order of operations see: /// + /// /// Useful to handle processing any transport-layer events such as processing inbound messages or changes in connection state(s). /// protected virtual void OnEarlyUpdate() @@ -146,6 +147,10 @@ internal void PostLateUpdate() OnPostLateUpdate(); } + /// + /// Invoked to acquire the network topology for the current network session. + /// + /// protected virtual NetworkTopologyTypes OnCurrentTopology() { return NetworkTopologyTypes.ClientServer; @@ -157,9 +162,21 @@ internal NetworkTopologyTypes CurrentTopology() } } + /// + /// The two network topology types supported by Netcode for GameObjects. + /// + /// + /// is only supported using . + /// public enum NetworkTopologyTypes { + /// + /// The traditional client-server network topology. + /// ClientServer, + /// + /// The distributed authorityy network topology only supported by . + /// DistributedAuthority } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index c3cd824489..f8db2e30d9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -357,6 +357,9 @@ public NetworkEndpoint ListenEndPoint } } + /// + /// Returns true if the end point address is of type . + /// public bool IsIpv6 => !string.IsNullOrEmpty(Address) && ParseNetworkEndpoint(Address, Port, true).Family == NetworkFamily.Ipv6; } @@ -427,6 +430,9 @@ private struct PacketLossCache internal static event Action TransportDisposed; internal NetworkDriver NetworkDriver => m_Driver; + /// + /// Provides access to the for this instance. + /// protected NetworkDriver m_Driver; /// @@ -590,6 +596,11 @@ private bool ClientBindAndConnect() return true; } + /// + /// Virtual method that is invoked during . + /// + /// The that the client is connecting to. + /// A representing the connection to the server, or an invalid connection if the connection attempt fails. protected virtual NetworkConnection Connect(NetworkEndpoint serverEndpoint) { return m_Driver.Connect(serverEndpoint); @@ -1572,7 +1583,7 @@ private void ConfigureSimulatorForUtp1() ); } #endif - + /// protected override NetworkTopologyTypes OnCurrentTopology() { return m_NetworkManager != null ? m_NetworkManager.NetworkConfig.NetworkTopology : NetworkTopologyTypes.ClientServer; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 6a58cea9a7..fc68b71b8b 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -251,7 +251,7 @@ protected void SetDistributedAuthorityProperties(NetworkManager networkManager) /// stages and can be used to log verbose information /// for troubleshooting an integration test. /// - /// + /// The debug message to be logged when verbose debugging is enabled [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void VerboseDebug(string msg) { @@ -686,7 +686,7 @@ protected void SetTimeTravelSimulatedLatencyJitter(float jitterSeconds) /// /// Creates the server and clients /// - /// + /// The number of client instances to create protected void CreateServerAndClients(int numberOfClients) { VerboseDebug($"Entering {nameof(CreateServerAndClients)}"); @@ -1800,7 +1800,7 @@ public NetcodeIntegrationTest(NetworkTopologyTypes networkTopologyType, HostOrSe /// public MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { } /// } /// - /// + /// Specifies whether to run the test as a Host or Server configuration public NetcodeIntegrationTest(HostOrServer hostOrServer) { m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost; @@ -1903,8 +1903,8 @@ protected IEnumerator WaitForTicks(NetworkManager networkManager, int count) /// This will only simulate the netcode update loop, as well as update events on /// NetworkBehaviour instances, and will not simulate any Unity update processes (physics, etc) /// - /// - /// + /// The total amount of time to simulate, in seconds + /// The number of frames to distribute the time across protected static void TimeTravel(double amountOfTimeInSeconds, int numFramesToSimulate) { var interval = amountOfTimeInSeconds / numFramesToSimulate; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index 1b4e09426a..2f5f975ecd 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -267,7 +267,8 @@ internal static NetworkManager CreateNewClient(int identifier, bool mockTranspor /// Used to add a client to the already existing list of clients /// /// The amount of clients - /// + /// Output array containing the created NetworkManager instances + /// When true, uses mock transport for testing, otherwise uses real transport. Default value is false public static bool CreateNewClients(int clientCount, out NetworkManager[] clients, bool useMockTransport = false) { clients = new NetworkManager[clientCount]; @@ -284,7 +285,8 @@ public static bool CreateNewClients(int clientCount, out NetworkManager[] client /// /// Stops one single client and makes sure to cleanup any static variables in this helper /// - /// + /// The NetworkManager instance to stop + /// When true, destroys the GameObject, when false, only shuts down the network connection. Default value is true public static void StopOneClient(NetworkManager clientToStop, bool destroy = true) { clientToStop.Shutdown(); @@ -299,7 +301,7 @@ public static void StopOneClient(NetworkManager clientToStop, bool destroy = tru /// /// Starts one single client and makes sure to register the required hooks and handlers /// - /// + /// The NetworkManager instance to start public static void StartOneClient(NetworkManager clientToStart) { clientToStart.StartClient(); @@ -453,7 +455,7 @@ internal static bool Start(bool host, bool startServer, NetworkManager server, N /// The Server NetworkManager /// The Clients NetworkManager /// called immediately after server is started and before client(s) are started - /// + /// True if all instances started successfully, false otherwise public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, BeforeClientStartCallback callback = null, bool startServer = true) { if (s_IsStarted) @@ -637,7 +639,7 @@ public static void MarkAsSceneObjectRoot(GameObject networkObjectRoot, NetworkMa /// /// The client /// The result. If null, it will automatically assert - /// The max frames to wait for + /// Maximum time in seconds to wait for connection. Defaults to DefaultTimeout public static IEnumerator WaitForClientConnected(NetworkManager client, ResultWrapper result = null, float timeout = DefaultTimeout) { yield return WaitForClientsConnected(new NetworkManager[] { client }, result, timeout); @@ -648,8 +650,7 @@ public static IEnumerator WaitForClientConnected(NetworkManager client, ResultWr /// /// The clients to be connected /// The result. If null, it will automatically assert - /// The max frames to wait for - /// + /// Maximum time in seconds to wait for connection. Defaults to DefaultTimeout public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, ResultWrapper result = null, float timeout = DefaultTimeout) { // Make sure none are the host client @@ -704,7 +705,7 @@ public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, Resu /// /// The server /// The result. If null, it will automatically assert - /// The max frames to wait for + /// Maximum time in seconds to wait for connection. Defaults to DefaultTimeout public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, ResultWrapper result = null, float timeout = DefaultTimeout) { yield return WaitForClientsConnectedToServer(server, server.IsHost ? s_ClientCount + 1 : s_ClientCount, result, timeout); @@ -715,7 +716,7 @@ public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, /// /// The server /// The result. If null, it will automatically assert - /// The max frames to wait for + /// Maximum time in seconds to wait for connection. Defaults to DefaultTimeout public static IEnumerator WaitForClientsConnectedToServer(NetworkManager server, int clientCount = 1, ResultWrapper result = null, float timeout = DefaultTimeout) { if (!server.IsServer) @@ -750,7 +751,7 @@ public static IEnumerator WaitForClientsConnectedToServer(NetworkManager server, /// The representation to get the object from /// The result /// Whether or not to fail if no object is found and result is null - /// The max frames to wait for + /// Maximum time in seconds to wait for connection. Defaults to DefaultTimeout public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId, NetworkManager representation, ResultWrapper result, bool failIfNull = true, float timeout = DefaultTimeout) { if (result == null) @@ -781,7 +782,7 @@ public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId /// The representation to get the object from /// The result /// Whether or not to fail if no object is found and result is null - /// The max frames to wait for + /// Maximum time in seconds to wait for connection. Defaults to DefaultTimeout public static IEnumerator GetNetworkObjectByRepresentation(Func predicate, NetworkManager representation, ResultWrapper result, bool failIfNull = true, float timeout = DefaultTimeout) { if (result == null) @@ -817,7 +818,7 @@ public static IEnumerator GetNetworkObjectByRepresentation(FuncThe representation to get the object from /// The result /// Whether or not to fail if no object is found and result is null - /// The max frames to wait for + /// The max frames to wait for public static void GetNetworkObjectByRepresentationWithTimeTravel(Func predicate, NetworkManager representation, ResultWrapper result, bool failIfNull = true, int maxTries = 60) { if (result == null) @@ -849,8 +850,8 @@ public static void GetNetworkObjectByRepresentationWithTimeTravel(Func /// The predicate to wait for /// The result. If null, it will fail if the predicate is not met + /// Maximum time in seconds to wait for connection. Defaults to DefaultTimeout /// The min frames to wait for - /// The max frames to wait for public static IEnumerator WaitForCondition(Func predicate, ResultWrapper result = null, float timeout = DefaultTimeout, int minFrames = DefaultMinFrames) { if (predicate == null) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkManagerHelper.cs index 23aad063dc..03d1d807b9 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkManagerHelper.cs @@ -93,7 +93,7 @@ public static bool StartNetworkManager(out NetworkManager networkManager, Networ /// Add a GameObject with a NetworkObject component /// /// the name of the object - /// + /// A unique identifier (GUID) for the newly created GameObject public static Guid AddGameNetworkObject(string nameOfGameObject) { var gameObjectId = Guid.NewGuid(); @@ -121,7 +121,7 @@ public static Guid AddGameNetworkObject(string nameOfGameObject) /// /// NetworkBehaviour component being added to the GameObject /// ID returned to reference the game object - /// + /// The newly added component instance of type T public static T AddComponentToObject(Guid gameObjectIdentifier) where T : NetworkBehaviour { Assert.IsTrue(InstantiatedGameObjects.ContainsKey(gameObjectIdentifier)); diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs index ec669aeb77..cf357ea3b8 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetworkVariableHelper.cs @@ -14,7 +14,7 @@ namespace Unity.Netcode.TestHelpers.Runtime /// /// From both we can then at least determine if the value indeed changed /// - /// + /// The type of value managed by the NetworkVariable public class NetworkVariableHelper : NetworkVariableBaseHelper { private readonly NetworkVariable m_NetworkVariable; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/TimeoutHelper.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/TimeoutHelper.cs index 1de7802444..a101873e6c 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/TimeoutHelper.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/TimeoutHelper.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.TestHelpers.Runtime { /// - /// Can be used independently or assigned to in the + /// Can be used independently or assigned to in the /// event the default timeout period needs to be adjusted /// public class TimeoutHelper diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs index 545b30456e..7ae68bb53f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs @@ -253,7 +253,7 @@ public static void OneTimeSetup(int numberOfInstances, GameObject prefabInstance /// /// Invoke when s are created but not started. /// - /// + /// The instance to assign a handler to. Must not be null and must not already have a handler assigned. public static void AssignHandler(NetworkManager networkManager) { if (s_PrefabInstanceHandlers.Count > 0) @@ -290,9 +290,9 @@ public static bool ValidatePersistedInstances(NetworkManager networkManager, int /// /// Releases back to the queue and if destroy is true it will completely - /// remove all references so they are cleaned up when + /// remove all references so they are cleaned up when /// - /// + /// If true, completely removes all references and cleans up instances. If false, returns handlers to the queue for reuse. public static void ReleaseAll(bool destroy = false) { foreach (var entry in s_AssignedInstances) @@ -333,6 +333,7 @@ public void Initialize(NetworkManager networkManager) /// /// This validates that the instances persisted to the next test set and persisted /// between network sessions + /// public bool ValidateInstanceSpawnCount(int minCount) { // First pass we should have no instances diff --git a/com.unity.netcode.gameobjects/pvpExceptions.json b/pvpExceptions.json similarity index 69% rename from com.unity.netcode.gameobjects/pvpExceptions.json rename to pvpExceptions.json index 988e4d7a6d..bda7ccbfe0 100644 --- a/com.unity.netcode.gameobjects/pvpExceptions.json +++ b/pvpExceptions.json @@ -1,249 +1,20 @@ { - "exempts": { + "exempts": { "PVP-41-1": { "errors": [ "CHANGELOG.md: line 9: Unreleased section is not allowed for public release" ] }, - "PVP-150-1": { - "errors": [ - "Unity.Netcode.Components.AnticipatedNetworkTransform: in list item context (only allowed in block or inline context)", - "Unity.Netcode.Components.AnticipatedNetworkTransform: in list item context (only allowed in block or inline context)", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateMove(Vector3): empty tag", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateRotate(Quaternion): empty tag", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateScale(Vector3): empty tag", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void AnticipateState(TransformState): empty tag", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void Smooth(TransformState, TransformState, float): empty tag", - "Unity.Netcode.AnticipatedNetworkVariable: in list item context (only allowed in block or inline context)", - "Unity.Netcode.AnticipatedNetworkVariable: in list item context (only allowed in block or inline context)", - "Unity.Netcode.AnticipatedNetworkVariable: void Anticipate(T): empty tag", - "Unity.Netcode.AnticipatedNetworkVariable: void Smooth(in T, in T, float, AnticipatedNetworkVariable.SmoothDelegate): empty tag", - "Unity.Netcode.BufferedLinearInterpolatorVector3: XML is not well-formed: Missing closing quotation mark for string literal", - "Unity.Netcode.BufferSerializer: void SerializeValue(ref NativeArray, Allocator): unexpected ", - "Unity.Netcode.BufferSerializer: bool PreCheck(int): empty tag", - "Unity.Netcode.BufferSerializer: bool PreCheck(int): empty tag", - "Unity.Netcode.BytePacker: in block context; use instead", - "Unity.Netcode.ByteUnpacker: in block context; use instead", - "Unity.Netcode.ByteUnpacker: void ReadValuePacked(FastBufferReader, out string): empty tag", - "Unity.Netcode.FastBufferReader: in block context; use instead", - "Unity.Netcode.FastBufferReader: .ctor(NativeArray, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", - "Unity.Netcode.FastBufferReader: .ctor(NativeArray, Allocator, int, int, Allocator): empty tag", - "Unity.Netcode.FastBufferReader: .ctor(byte*, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", - "Unity.Netcode.FastBufferReader: .ctor(byte*, Allocator, int, int, Allocator): empty tag", - "Unity.Netcode.FastBufferReader: .ctor(FastBufferWriter, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", - "Unity.Netcode.FastBufferReader: .ctor(FastBufferWriter, Allocator, int, int, Allocator): empty tag", - "Unity.Netcode.FastBufferReader: .ctor(FastBufferReader, Allocator, int, int, Allocator): in block context (only allowed in top-level context)", - "Unity.Netcode.FastBufferReader: .ctor(FastBufferReader, Allocator, int, int, Allocator): empty tag", - "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out T): empty tag", - "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out T): empty tag", - "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out T[]): empty tag", - "Unity.Netcode.FastBufferReader: void ReadNetworkSerializable(out NativeArray, Allocator): empty tag", - "Unity.Netcode.FastBufferReader: void ReadNetworkSerializableInPlace(ref T): empty tag", - "Unity.Netcode.FastBufferReader: void ReadNetworkSerializableInPlace(ref T): empty tag", - "Unity.Netcode.FastBufferReader: void ReadPartialValue(out T, int, int): empty tag", - "Unity.Netcode.FastBufferReader: void ReadValueSafe(out NativeArray, Allocator): unexpected ", - "Unity.Netcode.FastBufferReader: void ReadValueSafeTemp(out NativeArray): unexpected ", - "Unity.Netcode.FastBufferWriter: in block context; use instead", - "Unity.Netcode.FastBufferWriter: bool TryBeginWriteInternal(int): empty tag", - "Unity.Netcode.FastBufferWriter: bool TryBeginWriteInternal(int): empty tag", - "Unity.Netcode.FastBufferWriter: bool TryBeginWriteInternal(int): empty tag", - "Unity.Netcode.FastBufferWriter: byte[] ToArray(): empty tag", - "Unity.Netcode.FastBufferWriter: byte* GetUnsafePtr(): empty tag", - "Unity.Netcode.FastBufferWriter: byte* GetUnsafePtrAtCurrentPosition(): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(string, bool): empty tag", - "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(in T): empty tag", - "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(T[], int, int): empty tag", - "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(T[], int, int): empty tag", - "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(NativeArray, int, int): empty tag", - "Unity.Netcode.FastBufferWriter: void WriteNetworkSerializable(NativeArray, int, int): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(T[], int, int): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(T[], int, int): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(NativeArray, int, int): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(NativeArray, int, int): empty tag", - "Unity.Netcode.FastBufferWriter: void WritePartialValue(T, int, int): empty tag", - "Unity.Netcode.FastBufferWriter: void WritePartialValue(T, int, int): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T, ForStructs): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T, ForStructs): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T, ForStructs): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in T): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in NativeArray): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in NativeArray): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(in NativeArray): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(): empty tag", - "Unity.Netcode.FastBufferWriter: int GetWriteSize(): empty tag", - "Unity.Netcode.FastBufferWriter: void WriteValueSafe(in NativeArray): unexpected ", - "Unity.Netcode.ForceNetworkSerializeByMemcpy: empty tag", - "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: tag inside ", - "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: mixed block and inline content in ; wrap inline content in ", - "Unity.Netcode.Components.HalfVector3: .ctor(Vector3, bool3): unexpected ", - "Unity.Netcode.InvalidParentException: .ctor(string): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.InvalidParentException: .ctor(string): empty tag", - "Unity.Netcode.InvalidParentException: .ctor(string, Exception): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.InvalidParentException: .ctor(string, Exception): empty tag", - "Unity.Netcode.IReaderWriter: void SerializeValue(ref NativeArray, Allocator): unexpected ", - "Unity.Netcode.IReaderWriter: bool PreCheck(int): empty tag", - "Unity.Netcode.IReaderWriter: bool PreCheck(int): empty tag", - "Unity.Netcode.Editor.NetcodeEditorBase: void OnEnable(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void VerboseDebug(string): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void CreateServerAndClients(int): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: .ctor(HostOrServer): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void TimeTravel(double, int): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool CreateNewClients(int, out NetworkManager[], bool): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void StopOneClient(NetworkManager, bool): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void StartOneClient(NetworkManager): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Start(bool, NetworkManager, NetworkManager[], BeforeClientStartCallback, bool): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnected(NetworkManager, ResultWrapper, float): unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnected(NetworkManager[], ResultWrapper, float): unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnected(NetworkManager[], ResultWrapper, float): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnectedToServer(NetworkManager, ResultWrapper, float): unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(ulong, NetworkManager, ResultWrapper, bool, float): unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(Func, NetworkManager, ResultWrapper, bool, float): unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void GetNetworkObjectByRepresentationWithTimeTravel(Func, NetworkManager, ResultWrapper, bool, int): unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForCondition(Func, ResultWrapper, float, int): unexpected ", - "Unity.Netcode.NetworkBehaviour: NetworkObject: text or XML content outside a top-level tag", - "Unity.Netcode.NetworkBehaviour: void OnGainedOwnership(): mixed block and inline content in ; wrap inline content in ", - "Unity.Netcode.NetworkBehaviour: void OnLostOwnership(): mixed block and inline content in ; wrap inline content in ", - "Unity.Netcode.NetworkBehaviour: NetworkObject GetNetworkObject(ulong): empty tag", - "Unity.Netcode.NetworkBehaviour: NetworkObject GetNetworkObject(ulong): empty tag", - "Unity.Netcode.NetworkBehaviour: void OnSynchronize(ref BufferSerializer): unexpected ", - "Unity.Netcode.NetworkBehaviourReference: .ctor(NetworkBehaviour): empty tag", - "Unity.Netcode.NetworkConfig: string ToBase64(): empty tag", - "Unity.Netcode.NetworkConfig: ulong GetConfig(bool): empty tag", - "Unity.Netcode.NetworkConfig: ulong GetConfig(bool): empty tag", - "Unity.Netcode.NetworkConfig: bool CompareConfig(ulong): empty tag", - "Unity.Netcode.NetworkConfig: bool CompareConfig(ulong): empty tag", - "Unity.Netcode.NetworkList: .ctor(IEnumerable, NetworkVariableReadPermission, NetworkVariableWritePermission): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: .ctor(IEnumerable, NetworkVariableReadPermission, NetworkVariableWritePermission): empty tag", - "Unity.Netcode.NetworkList: IEnumerator GetEnumerator(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: void Add(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: void Clear(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: bool Contains(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: bool Remove(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: Count: cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: int IndexOf(T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: void Insert(int, T): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: void RemoveAt(int): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkList: this[int]: cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkManager: OnServerStopped: unexpected ", - "Unity.Netcode.NetworkManager: OnClientStopped: unexpected ", - "Unity.Netcode.NetworkManager: void AddNetworkPrefab(GameObject): empty tag", - "Unity.Netcode.NetworkManager: void AddNetworkPrefab(GameObject): empty tag", - "Unity.Netcode.NetworkManager: void RemoveNetworkPrefab(GameObject): empty tag", - "Unity.Netcode.NetworkManager: MaximumTransmissionUnitSize: empty tag", - "Unity.Netcode.NetworkManager: MaximumTransmissionUnitSize: unexpected ", - "Unity.Netcode.NetworkManager: void SetPeerMTU(ulong, int): empty tag", - "Unity.Netcode.NetworkManager: int GetPeerMTU(ulong): empty tag", - "Unity.Netcode.NetworkManager: int GetPeerMTU(ulong): empty tag", - "Unity.Netcode.NetworkManager: MaximumFragmentedMessageSize: empty tag", - "Unity.Netcode.NetworkManager: MaximumFragmentedMessageSize: unexpected ", - "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: Guid AddGameNetworkObject(string): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: T AddComponentToObject(Guid): empty tag", - "Unity.Netcode.NetworkObject: NetworkTransforms: must use self-closing syntax", - "Unity.Netcode.NetworkObject: bool TryRemoveParent(bool): empty tag", - "Unity.Netcode.NetworkObject.OnOwnershipPermissionsFailureDelegateHandler: empty tag", - "Unity.Netcode.NetworkObject.OnOwnershipRequestedDelegateHandler: empty tag", - "Unity.Netcode.NetworkObject.OnOwnershipRequestedDelegateHandler: empty tag", - "Unity.Netcode.NetworkObject.OnOwnershipRequestResponseDelegateHandler: empty tag", - "Unity.Netcode.NetworkObjectReference: .ctor(NetworkObject): empty tag", - "Unity.Netcode.NetworkObjectReference: .ctor(GameObject): empty tag", - "Unity.Netcode.INetworkPrefabInstanceHandler: NetworkObject Instantiate(ulong, Vector3, Quaternion): empty tag", - "Unity.Netcode.NetworkPrefabHandler: bool AddHandler(NetworkObject, INetworkPrefabInstanceHandler): empty tag", - "Unity.Netcode.NetworkPrefabHandler: bool AddHandler(uint, INetworkPrefabInstanceHandler): empty tag", - "Unity.Netcode.NetworkPrefabHandler: void AddNetworkPrefab(GameObject): empty tag", - "Unity.Netcode.NetworkPrefabHandler: void AddNetworkPrefab(GameObject): empty tag", - "Unity.Netcode.NetworkPrefabHandler: void RemoveNetworkPrefab(GameObject): empty tag", - "Unity.Netcode.NetworkPrefabsList: void Add(NetworkPrefab): empty tag", - "Unity.Netcode.NetworkPrefabsList: void Remove(NetworkPrefab): empty tag", - "Unity.Netcode.SceneEvent: in block context; use instead", - "Unity.Netcode.NetworkSceneManager: SceneEventProgressStatus UnloadScene(Scene): empty tag", - "Unity.Netcode.NetworkSceneManager.SceneEventDelegate: in block context; use instead", - "Unity.Netcode.NetworkSceneManager.SceneEventDelegate: empty tag", - "Unity.Netcode.NetworkSceneManager.OnLoadDelegateHandler: in block context; use instead", - "Unity.Netcode.NetworkSceneManager.OnUnloadDelegateHandler: in block context; use instead", - "Unity.Netcode.NetworkSceneManager.OnSynchronizeDelegateHandler: in block context; use instead", - "Unity.Netcode.NetworkSceneManager.OnEventCompletedDelegateHandler: in block context; use instead", - "Unity.Netcode.NetworkSceneManager.OnLoadCompleteDelegateHandler: in block context; use instead", - "Unity.Netcode.NetworkSceneManager.OnUnloadCompleteDelegateHandler: in block context; use instead", - "Unity.Netcode.NetworkSceneManager.OnSynchronizeCompleteDelegateHandler: in block context; use instead", - "Unity.Netcode.NetworkTime: NetworkTime TimeTicksAgo(int, float): empty tag", - "Unity.Netcode.NetworkTimeSystem: bool Advance(double): empty tag", - "Unity.Netcode.Components.NetworkTransform: void OnSynchronize(ref BufferSerializer): empty tag", - "Unity.Netcode.Components.NetworkTransform: void OnSynchronize(ref BufferSerializer): empty tag", - "Unity.Netcode.Components.NetworkTransform: void OnSynchronize(ref BufferSerializer): unexpected ", - "Unity.Netcode.Components.NetworkTransform: void OnInitialize(ref NetworkVariable): empty tag", - "Unity.Netcode.Components.NetworkTransform: void SetState(Vector3?, Quaternion?, Vector3?, bool): empty tag", - "Unity.Netcode.Components.NetworkTransform: void SetState(Vector3?, Quaternion?, Vector3?, bool): empty tag", - "Unity.Netcode.Components.NetworkTransform: void SetState(Vector3?, Quaternion?, Vector3?, bool): text or XML content outside a top-level tag", - "Unity.Netcode.Components.NetworkTransform: void Teleport(Vector3, Quaternion, Vector3): empty tag", - "Unity.Netcode.Components.NetworkTransform: void Teleport(Vector3, Quaternion, Vector3): empty tag", - "Unity.Netcode.Components.NetworkTransform: void Teleport(Vector3, Quaternion, Vector3): text or XML content outside a top-level tag", - "Unity.Netcode.Components.NetworkTransform: void OnUpdate(): cannot auto-inheritdoc (not an override or interface implementation); must specify 'cref'", - "Unity.Netcode.NetworkTransport: in block context; use instead", - "Unity.Netcode.NetworkTransport: void Initialize(NetworkManager): suspicious '///' triple-slash inside XmlDoc comment", - "Unity.Netcode.NetworkTransport: void Initialize(NetworkManager): text or XML content outside a top-level tag", - "Unity.Netcode.NetworkTransport: void OnEarlyUpdate(): XML is not well-formed: End tag was not expected at this location", - "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): in block context (only allowed in top-level context)", - "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): empty tag", - "Unity.Netcode.NetworkVariableBase: void SetUpdateTraits(NetworkVariableUpdateTraits): empty tag", - "Unity.Netcode.NetworkVariableBase: bool ExceedsDirtinessThreshold(): empty tag", - "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: empty tag", - "Unity.Netcode.NetworkVariableSerialization: void Write(FastBufferWriter, ref T): empty tag", - "Unity.Netcode.NetworkVariableSerialization: void Read(FastBufferReader, ref T): empty tag", - "Unity.Netcode.NetworkVariableSerialization: void WriteDelta(FastBufferWriter, ref T, ref T): empty tag", - "Unity.Netcode.NetworkVariableSerialization: void ReadDelta(FastBufferReader, ref T): empty tag", - "Unity.Netcode.NetworkVariableSerialization: void Duplicate(in T, ref T): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Single(ulong, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Single(ulong, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeArray, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeArray, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeList, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(NativeList, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(ulong[], RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(ulong[], RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(T, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(T, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeArray, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeArray, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeList, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(NativeList, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong[], RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(ulong[], RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(T, RpcTargetUse): empty tag", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(T, RpcTargetUse): empty tag", - "Unity.Netcode.SceneEventType: in block context; use instead", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: must use self-closing syntax", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedByMemcpy(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedByMemcpyArray(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_List(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_HashSet(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): unexpected ", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedINetworkSerializable(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_UnmanagedINetworkSerializableArray(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_ManagedINetworkSerializable(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_FixedString(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_FixedStringArray(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_ManagedIEquatable(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedIEquatable(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedIEquatableArray(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_List(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_HashSet(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): unexpected ", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedValueEquals(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_UnmanagedValueEqualsArray(): empty tag", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_ManagedClassEquals(): empty tag", - "Unity.Netcode.UserNetworkVariableSerialization: empty tag", - "Unity.Netcode.UserNetworkVariableSerialization.DuplicateValueDelegate: unexpected " - ] - }, "PVP-151-1": { "errors": [ + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: undocumented", + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: NetworkPrefabsPath: undocumented", + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: TempNetworkPrefabsPath: undocumented", + "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: GenerateDefaultNetworkPrefabs: undocumented", + "Unity.Netcode.Editor.Configuration.NetworkPrefabProcessor: DefaultNetworkPrefabsPath: undocumented", + "Unity.Netcode.Editor.NetworkPrefabsEditor: undocumented", + "Unity.Netcode.Editor.NetworkPrefabsEditor: void OnInspectorGUI(): undocumented", + "Unity.Netcode.Editor.NetcodeEditorBase: missing ", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnUpdate(): undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkSpawn(): undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkDespawn(): undocumented", @@ -255,6 +26,123 @@ "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Position: undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Rotation: undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Scale: undocumented", + "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.BufferedLinearInterpolator: m_InterpStartValue: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: m_CurrentInterpValue: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: m_InterpEndValue: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: m_Buffer: undocumented", + "Unity.Netcode.BufferedLinearInterpolator: void OnConvertTransformSpace(Transform, bool): undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: Item: undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: TimeSent: undocumented", + "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: .ctor(T, double): undocumented", + "Unity.Netcode.BufferedLinearInterpolatorQuaternion: void OnConvertTransformSpace(Transform, bool): undocumented", + "Unity.Netcode.BufferedLinearInterpolatorVector3: void OnConvertTransformSpace(Transform, bool): undocumented", + "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetConvertedDelta(): missing ", + "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetDeltaPosition(): missing ", + "Unity.Netcode.Components.NetworkTransform: AuthorityMode: undocumented", + "Unity.Netcode.Components.NetworkTransform: PositionInLocalSpace: undocumented", + "Unity.Netcode.Components.NetworkTransform: RotationInLocalSpace: undocumented", + "Unity.Netcode.Components.NetworkTransform: void OnTransformUpdated(): undocumented", + "Unity.Netcode.Components.NetworkTransform: void OnBeforeUpdateTransformState(): undocumented", + "Unity.Netcode.Components.NetworkTransform: void OnOwnershipChanged(ulong, ulong): undocumented", + "Unity.Netcode.Components.NetworkTransform: float GetTickLatency(): missing ", + "Unity.Netcode.Components.NetworkTransform: float GetTickLatencyInSeconds(): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsUnreliableFrameSync(): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsReliableStateUpdate(): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", + "Unity.Netcode.Components.NetworkTransform.AuthorityModes: undocumented", + "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Server: undocumented", + "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Owner: undocumented", + "Unity.Netcode.NetworkConfig: Prefabs: undocumented", + "Unity.Netcode.NetworkConfig: NetworkTopology: undocumented", + "Unity.Netcode.NetworkConfig: UseCMBService: undocumented", + "Unity.Netcode.NetworkConfig: AutoSpawnPlayerPrefabClientSide: undocumented", + "Unity.Netcode.NetworkPrefab: bool Equals(NetworkPrefab): undocumented", + "Unity.Netcode.NetworkPrefab: SourcePrefabGlobalObjectIdHash: undocumented", + "Unity.Netcode.NetworkPrefab: TargetPrefabGlobalObjectIdHash: undocumented", + "Unity.Netcode.NetworkPrefab: bool Validate(int): undocumented", + "Unity.Netcode.NetworkPrefab: string ToString(): undocumented", + "Unity.Netcode.NetworkPrefabs: Prefabs: undocumented", + "Unity.Netcode.NetworkPrefabs: void Finalize(): undocumented", + "Unity.Netcode.NetworkPrefabs: void Initialize(bool): missing ", + "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", + "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", + "Unity.Netcode.NetworkPrefabs: void Remove(NetworkPrefab): missing ", + "Unity.Netcode.NetworkPrefabs: void Remove(GameObject): missing ", + "Unity.Netcode.NetworkClient: NetworkTopologyType: undocumented", + "Unity.Netcode.NetworkClient: DAHost: undocumented", + "Unity.Netcode.ConnectionEventData: EventType: undocumented", + "Unity.Netcode.RpcException: undocumented", + "Unity.Netcode.RpcException: .ctor(string): undocumented", + "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkPostSpawn(): undocumented", + "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkSessionSynchronized(): undocumented", + "Unity.Netcode.NetworkBehaviour: void OnReanticipate(double): undocumented", + "Unity.Netcode.NetworkManager: CurrentSessionOwner: undocumented", + "Unity.Netcode.NetworkManager: void NetworkUpdate(NetworkUpdateStage): undocumented", + "Unity.Netcode.NetworkManager.ReanticipateDelegate: undocumented", + "Unity.Netcode.NetworkObject: void SetSceneObjectStatus(bool): undocumented", + "Unity.Netcode.NetworkObject: ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour): undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: None: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: Distributable: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: Transferable: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: RequestRequired: undocumented", + "Unity.Netcode.NetworkObject.OwnershipStatus: SessionOwner: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: Locked: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestRequired: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestInProgress: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: NotTransferrable: undocumented", + "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: SessionOwnerOnly: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestSent: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: AlreadyOwner: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestRequiredNotSet: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: Locked: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestInProgress: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestStatus: SessionOwnerOnly: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Approved: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Locked: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: RequestInProgress: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: CannotRequest: undocumented", + "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Denied: undocumented", + "Unity.Netcode.NetworkObject.OwnershipLockActions: None: undocumented", + "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndLock: undocumented", + "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndUnlock: undocumented", + "Unity.Netcode.NetworkObject.VisibilityDelegate: missing ", + "Unity.Netcode.NetworkObject.SpawnDelegate: missing ", + "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", + "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", + "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: .ctor(int): undocumented", + "Unity.Netcode.GenerateSerializationForTypeAttribute: .ctor(Type): undocumented", + "Unity.Netcode.RpcAttribute: RequireOwnership: undocumented", + "Unity.Netcode.RpcAttribute: DeferLocal: undocumented", + "Unity.Netcode.RpcAttribute: AllowTargetOverride: undocumented", + "Unity.Netcode.RpcAttribute: .ctor(SendTo): undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: Delivery: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: RequireOwnership: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: DeferLocal: undocumented", + "Unity.Netcode.RpcAttribute.RpcAttributeParams: AllowTargetOverride: undocumented", + "Unity.Netcode.ServerRpcAttribute: RequireOwnership: undocumented", + "Unity.Netcode.ServerRpcAttribute: .ctor(): undocumented", + "Unity.Netcode.ClientRpcAttribute: .ctor(): undocumented", + "Unity.Netcode.LocalDeferMode: undocumented", + "Unity.Netcode.LocalDeferMode: Default: undocumented", + "Unity.Netcode.LocalDeferMode: Defer: undocumented", + "Unity.Netcode.LocalDeferMode: SendImmediate: undocumented", + "Unity.Netcode.RpcSendParams: Target: undocumented", + "Unity.Netcode.RpcSendParams: LocalDeferMode: undocumented", + "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(BaseRpcTarget): undocumented", + "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(LocalDeferMode): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcSendParams): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(BaseRpcTarget): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(LocalDeferMode): undocumented", + "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcReceiveParams): undocumented", "Unity.Netcode.StaleDataHandling: undocumented", "Unity.Netcode.StaleDataHandling: Ignore: undocumented", "Unity.Netcode.StaleDataHandling: Reanticipate: undocumented", @@ -275,22 +163,37 @@ "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", - "Unity.Netcode.BaseRpcTarget: undocumented", - "Unity.Netcode.BaseRpcTarget: m_NetworkManager: undocumented", - "Unity.Netcode.BaseRpcTarget: void CheckLockBeforeDispose(): undocumented", - "Unity.Netcode.BaseRpcTarget: void Dispose(): undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_InterpStartValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_CurrentInterpValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_InterpEndValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_Buffer: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: Item: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: TimeSent: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: .ctor(T, double): undocumented", - "Unity.Netcode.BufferedLinearInterpolatorQuaternion: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.BufferedLinearInterpolatorVector3: XML is not well-formed: Missing closing quotation mark for string literal", - "Unity.Netcode.BufferedLinearInterpolatorVector3: void OnConvertTransformSpace(Transform, bool): undocumented", + "Unity.Netcode.NetworkList: void Finalize(): undocumented", + "Unity.Netcode.NetworkVariable: CheckExceedsDirtinessThreshold: undocumented", + "Unity.Netcode.NetworkVariable: bool ExceedsDirtinessThreshold(): undocumented", + "Unity.Netcode.NetworkVariable: void OnInitialize(): undocumented", + "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): missing ", + "Unity.Netcode.NetworkVariable: void Dispose(): undocumented", + "Unity.Netcode.NetworkVariable: void Finalize(): undocumented", + "Unity.Netcode.NetworkVariable.CheckExceedsDirtinessThresholdDelegate: undocumented", + "Unity.Netcode.NetworkVariableUpdateTraits: undocumented", + "Unity.Netcode.NetworkVariableUpdateTraits: MinSecondsBetweenUpdates: undocumented", + "Unity.Netcode.NetworkVariableUpdateTraits: MaxSecondsBetweenUpdates: undocumented", + "Unity.Netcode.NetworkVariableBase: NetworkBehaviour GetBehaviour(): undocumented", + "Unity.Netcode.NetworkVariableBase: void MarkNetworkBehaviourDirty(): undocumented", + "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", + "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", + "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", + "Unity.Netcode.UserNetworkVariableSerialization.WriteDeltaDelegate: missing ", + "Unity.Netcode.UserNetworkVariableSerialization.DuplicateValueDelegate: missing ", + "Unity.Netcode.NetworkSpawnManager: void Finalize(): undocumented", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnNetworkSpawn(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void RegisterAndLabelNetworkObject(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void DeRegisterNetworkObject(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnLostOwnership(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnGainedOwnership(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnDestroy(): undocumented", "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: TimedOut: undocumented", "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: bool OnHasConditionBeenReached(): undocumented", "Unity.Netcode.TestHelpers.Runtime.ConditionalPredicateBase: bool HasConditionBeenReached(): undocumented", @@ -301,21 +204,6 @@ "Unity.Netcode.TestHelpers.Runtime.IConditionalPredicate: undocumented", "Unity.Netcode.TestHelpers.Runtime.IConditionalPredicate: bool HasConditionBeenReached(): missing ", "Unity.Netcode.TestHelpers.Runtime.IConditionalPredicate: void Finished(bool): missing ", - "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", - "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", - "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: undocumented", - "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: NumberOfClients: undocumented", - "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: .ctor(HostOrServer, bool): undocumented", - "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: void OnServerAndClientsCreated(): undocumented", - "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: void OnNewClientCreated(NetworkManager): undocumented", - "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: IEnumerator HiddenObjectPromotedSessionOwnerNewClientSynchronizes(): missing ", - "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: .ctor(int): undocumented", - "Unity.Netcode.GenerateSerializationForTypeAttribute: .ctor(Type): undocumented", - "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector3: .ctor(Vector3, bool3): missing ", - "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: undocumented", "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: string GetVector3Values(ref Vector3): undocumented", "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: string GetVector3Values(Vector3): undocumented", @@ -333,8 +221,6 @@ "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: .ctor(NetworkTopologyTypes): undocumented", "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: .ctor(HostOrServer): undocumented", "Unity.Netcode.TestHelpers.Runtime.IntegrationTestWithApproximation: .ctor(): undocumented", - "Unity.Netcode.InvalidParentException: .ctor(string): missing ", - "Unity.Netcode.InvalidParentException: .ctor(string, Exception): missing ", "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: undocumented", "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: AllMessagesReceived: undocumented", "Unity.Netcode.TestHelpers.Runtime.MessageHooksConditional: NumberOfMessagesReceived: undocumented", @@ -350,6 +236,11 @@ "Unity.Netcode.TestHelpers.Runtime.MessageHookEntry: m_NetworkManager: undocumented", "Unity.Netcode.TestHelpers.Runtime.MessageHookEntry: void Initialize(): undocumented", "Unity.Netcode.TestHelpers.Runtime.MessageHookEntry: .ctor(NetworkManager, ReceiptType): undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: OnServerRpcAction: undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: OnClientRpcAction: undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: void MyServerRpc(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: void MyClientRpc(ClientRpcParams): undocumented", "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: undocumented", "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: RealTimeSinceStartup: undocumented", "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: UnscaledTime: undocumented", @@ -362,12 +253,6 @@ "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: StaticDeltaTime: undocumented", "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: void TimeTravel(double): undocumented", "Unity.Netcode.TestHelpers.Runtime.MockTimeProvider: void Reset(): undocumented", - "Unity.Netcode.Editor.NetcodeEditorBase: missing ", - "Unity.Netcode.Editor.NetcodeEditorBase: void OnEnable(): missing ", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: NetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: TempNetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: GenerateDefaultNetworkPrefabs: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: k_TickFrequency: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: s_GlobalTimeoutHelper: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: s_DefaultWaitForTick: undocumented", @@ -482,29 +367,20 @@ "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: NetworkManager CreateServer(bool): undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Create(int, out NetworkManager, out NetworkManager[], int, bool, bool): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Create(int, out NetworkManager, out NetworkManager[], int, bool, bool): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool CreateNewClients(int, out NetworkManager[], bool): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool CreateNewClients(int, out NetworkManager[], bool): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void StopOneClient(NetworkManager, bool): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: bool Start(bool, NetworkManager, NetworkManager[], BeforeClientStartCallback, bool): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: uint GetNextGlobalIdHashValue(): undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IsNetcodeIntegrationTestRunning: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void RegisterNetcodeIntegrationTest(bool): undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: GameObject CreateNetworkObjectPrefab(string, NetworkManager, params NetworkManager[]): undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void MarkAsSceneObjectRoot(GameObject, NetworkManager, NetworkManager[]): undocumented", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnected(NetworkManager, ResultWrapper, float): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnected(NetworkManager, ResultWrapper, float): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnected(NetworkManager[], ResultWrapper, float): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnectedToServer(NetworkManager, ResultWrapper, float): missing ", + "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnected(NetworkManager[], ResultWrapper, float): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientConnectedToServer(NetworkManager, ResultWrapper, float): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForClientsConnectedToServer(NetworkManager, int, ResultWrapper, float): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(ulong, NetworkManager, ResultWrapper, bool, float): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(ulong, NetworkManager, ResultWrapper, bool, float): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(Func, NetworkManager, ResultWrapper, bool, float): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator GetNetworkObjectByRepresentation(Func, NetworkManager, ResultWrapper, bool, float): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: void GetNetworkObjectByRepresentationWithTimeTravel(Func, NetworkManager, ResultWrapper, bool, int): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForCondition(Func, ResultWrapper, float, int): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: IEnumerator WaitForCondition(Func, ResultWrapper, float, int): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers: uint GetGlobalObjectIdHash(NetworkObject): undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTestHelpers.MessageHandleCheck: undocumented", @@ -522,49 +398,6 @@ "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void LogWasReceived(LogType, Regex): undocumented", "Unity.Netcode.RuntimeTests.NetcodeLogAssert: bool HasLogBeenReceived(LogType, string): undocumented", "Unity.Netcode.RuntimeTests.NetcodeLogAssert: void Reset(): undocumented", - "Unity.Netcode.RpcException: undocumented", - "Unity.Netcode.RpcException: .ctor(string): undocumented", - "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkPostSpawn(): undocumented", - "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkSessionSynchronized(): undocumented", - "Unity.Netcode.NetworkBehaviour: void OnReanticipate(double): undocumented", - "Unity.Netcode.NetworkClient: NetworkTopologyType: undocumented", - "Unity.Netcode.NetworkClient: DAHost: undocumented", - "Unity.Netcode.NetworkConfig: Prefabs: undocumented", - "Unity.Netcode.NetworkConfig: NetworkTopology: undocumented", - "Unity.Netcode.NetworkConfig: UseCMBService: undocumented", - "Unity.Netcode.NetworkConfig: AutoSpawnPlayerPrefabClientSide: undocumented", - "Unity.Netcode.ConnectionEventData: EventType: undocumented", - "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetConvertedDelta(): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetDeltaPosition(): missing ", - "Unity.Netcode.NetworkList: .ctor(IEnumerable, NetworkVariableReadPermission, NetworkVariableWritePermission): missing ", - "Unity.Netcode.NetworkList: void Finalize(): undocumented", - "Unity.Netcode.NetworkList: IEnumerator GetEnumerator(): missing ", - "Unity.Netcode.NetworkList: IEnumerator GetEnumerator(): missing ", - "Unity.Netcode.NetworkList: void Add(T): missing ", - "Unity.Netcode.NetworkList: void Add(T): missing ", - "Unity.Netcode.NetworkList: void Clear(): missing ", - "Unity.Netcode.NetworkList: bool Contains(T): missing ", - "Unity.Netcode.NetworkList: bool Contains(T): missing ", - "Unity.Netcode.NetworkList: bool Contains(T): missing ", - "Unity.Netcode.NetworkList: bool Remove(T): missing ", - "Unity.Netcode.NetworkList: bool Remove(T): missing ", - "Unity.Netcode.NetworkList: bool Remove(T): missing ", - "Unity.Netcode.NetworkList: Count: missing ", - "Unity.Netcode.NetworkList: int IndexOf(T): missing ", - "Unity.Netcode.NetworkList: int IndexOf(T): missing ", - "Unity.Netcode.NetworkList: int IndexOf(T): missing ", - "Unity.Netcode.NetworkList: void Insert(int, T): missing ", - "Unity.Netcode.NetworkList: void Insert(int, T): missing ", - "Unity.Netcode.NetworkList: void Insert(int, T): missing ", - "Unity.Netcode.NetworkList: void RemoveAt(int): missing ", - "Unity.Netcode.NetworkList: void RemoveAt(int): missing ", - "Unity.Netcode.NetworkList: this[int]: missing ", - "Unity.Netcode.NetworkList: this[int]: missing ", - "Unity.Netcode.NetworkManager: CurrentSessionOwner: undocumented", - "Unity.Netcode.NetworkManager: void NetworkUpdate(NetworkUpdateStage): undocumented", - "Unity.Netcode.NetworkManager.ReanticipateDelegate: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: NetworkManagerObject: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: NetworkManagerGameObject: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper: InstantiatedGameObjects: undocumented", @@ -577,102 +410,66 @@ "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper.NetworkManagerOperatingMode: Host: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper.NetworkManagerOperatingMode: Server: undocumented", "Unity.Netcode.TestHelpers.Runtime.NetworkManagerHelper.NetworkManagerOperatingMode: Client: undocumented", - "Unity.Netcode.NetworkObject: void SetSceneObjectStatus(bool): undocumented", - "Unity.Netcode.NetworkObject: ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour): undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: None: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: Distributable: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: Transferable: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: RequestRequired: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: SessionOwner: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: Locked: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestRequired: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestInProgress: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: NotTransferrable: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: SessionOwnerOnly: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestSent: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: AlreadyOwner: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestRequiredNotSet: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: Locked: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestInProgress: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: SessionOwnerOnly: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Approved: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Locked: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: RequestInProgress: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: CannotRequest: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Denied: undocumented", - "Unity.Netcode.NetworkObject.OwnershipLockActions: None: undocumented", - "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndLock: undocumented", - "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndUnlock: undocumented", - "Unity.Netcode.NetworkObject.VisibilityDelegate: missing ", - "Unity.Netcode.NetworkObject.SpawnDelegate: missing ", - "Unity.Netcode.NetworkPrefab: bool Equals(NetworkPrefab): undocumented", - "Unity.Netcode.NetworkPrefab: SourcePrefabGlobalObjectIdHash: undocumented", - "Unity.Netcode.NetworkPrefab: TargetPrefabGlobalObjectIdHash: undocumented", - "Unity.Netcode.NetworkPrefab: bool Validate(int): undocumented", - "Unity.Netcode.NetworkPrefab: string ToString(): undocumented", - "Unity.Netcode.Editor.Configuration.NetworkPrefabProcessor: DefaultNetworkPrefabsPath: undocumented", - "Unity.Netcode.NetworkPrefabs: Prefabs: undocumented", - "Unity.Netcode.NetworkPrefabs: void Finalize(): undocumented", - "Unity.Netcode.NetworkPrefabs: void Initialize(bool): missing ", - "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", - "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", - "Unity.Netcode.NetworkPrefabs: void Remove(NetworkPrefab): missing ", - "Unity.Netcode.NetworkPrefabs: void Remove(GameObject): missing ", - "Unity.Netcode.Editor.NetworkPrefabsEditor: undocumented", - "Unity.Netcode.Editor.NetworkPrefabsEditor: void OnInspectorGUI(): undocumented", - "Unity.Netcode.NetworkSceneManager: List GetSceneMapping(MapTypes): undocumented", - "Unity.Netcode.NetworkSceneManager.MapTypes: undocumented", - "Unity.Netcode.NetworkSceneManager.MapTypes: ServerToClient: undocumented", - "Unity.Netcode.NetworkSceneManager.MapTypes: ClientToServer: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: MapType: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: Scene: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: ScenePresent: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: SceneName: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: ServerHandle: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: MappedLocalHandle: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: LocalHandle: undocumented", - "Unity.Netcode.NetworkSceneManager.SceneMap: void NetworkSerialize(BufferSerializer): undocumented", - "Unity.Netcode.NetworkSpawnManager: List GetConnectedPlayers(): undocumented", - "Unity.Netcode.NetworkSpawnManager: void InternalOnOwnershipChanged(ulong, ulong): undocumented", - "Unity.Netcode.NetworkTime: NetworkTime TimeTicksAgo(int, float): missing ", - "Unity.Netcode.Components.NetworkTransform: AuthorityMode: undocumented", - "Unity.Netcode.Components.NetworkTransform: PositionInLocalSpace: undocumented", - "Unity.Netcode.Components.NetworkTransform: RotationInLocalSpace: undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnTransformUpdated(): undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnBeforeUpdateTransformState(): undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnOwnershipChanged(ulong, ulong): undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnUpdate(): missing ", - "Unity.Netcode.Components.NetworkTransform: float GetTickLatency(): missing ", - "Unity.Netcode.Components.NetworkTransform: float GetTickLatencyInSeconds(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsUnreliableFrameSync(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsReliableStateUpdate(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: undocumented", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Server: undocumented", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Owner: undocumented", - "Unity.Netcode.NetworkTransport: void OnEarlyUpdate(): XML is not well-formed: End tag was not expected at this location", - "Unity.Netcode.NetworkTransport: NetworkTopologyTypes OnCurrentTopology(): undocumented", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", - "Unity.Netcode.NetworkTopologyTypes: undocumented", - "Unity.Netcode.NetworkTopologyTypes: ClientServer: undocumented", - "Unity.Netcode.NetworkTopologyTypes: DistributedAuthority: undocumented", - "Unity.Netcode.NetworkVariable: CheckExceedsDirtinessThreshold: undocumented", - "Unity.Netcode.NetworkVariable: bool ExceedsDirtinessThreshold(): undocumented", - "Unity.Netcode.NetworkVariable: void OnInitialize(): undocumented", - "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): missing ", - "Unity.Netcode.NetworkVariable: void Dispose(): undocumented", - "Unity.Netcode.NetworkVariable: void Finalize(): undocumented", - "Unity.Netcode.NetworkVariable.CheckExceedsDirtinessThresholdDelegate: undocumented", - "Unity.Netcode.NetworkVariableUpdateTraits: undocumented", - "Unity.Netcode.NetworkVariableUpdateTraits: MinSecondsBetweenUpdates: undocumented", - "Unity.Netcode.NetworkVariableUpdateTraits: MaxSecondsBetweenUpdates: undocumented", - "Unity.Netcode.NetworkVariableBase: NetworkBehaviour GetBehaviour(): undocumented", - "Unity.Netcode.NetworkVariableBase: void MarkNetworkBehaviourDirty(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: OnValueChanged: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: .ctor(NetworkVariableBase): undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper.OnMyValueChangedDelegateHandler: undocumented", + "Unity.Netcode.TestHelpers.Runtime.NetworkVariableBaseHelper: .ctor(NetworkVariableBase): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: k_DefaultTimeOutWaitPeriod: undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: m_IsStarted: undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: TimedOut: undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: float GetTimeElapsed(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void OnStart(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void Start(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void OnStop(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void Stop(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: bool OnHasTimedOut(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: bool HasTimedOut(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: .ctor(float): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: int GetFrameCount(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: void OnStop(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: bool OnHasTimedOut(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: void OnStart(): undocumented", + "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: .ctor(float, uint): undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: NumberOfClients: undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: .ctor(HostOrServer, bool): undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: void OnServerAndClientsCreated(): undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: void OnNewClientCreated(NetworkManager): undocumented", + "Unity.Netcode.RuntimeTests.ExtendedNetworkShowAndHideTests: IEnumerator HiddenObjectPromotedSessionOwnerNewClientSynchronizes(): missing ", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: NumberOfClients: undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: .ctor(HostOrServer): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: IEnumerator OnSetup(): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: void OnCreatePlayerPrefab(): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: IEnumerator ProxyDoesNotInvokeOnSender(): undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: ReceivedRpc: undocumented", + "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: void SendToEveryOneButMe(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: NumberOfClients: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: .ctor(NetworkTopologyTypes, HostOrServer): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: void OnOneTimeSetup(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: void OnServerAndClientsCreated(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: IEnumerator PrefabSessionIstantiationPass(int): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: IEnumerator OnTearDown(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted: void OnOneTimeTearDown(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.TestBehaviour: TestNetworkVariable: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.TestBehaviour: LastUpdateDelta: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.TestBehaviour: SpawnedCount: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.TestBehaviour: void OnNetworkSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.TestBehaviour: void OnNetworkPostSpawn(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.TestBehaviour: void OnDestroy(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.TestBehaviour: void OnNetworkDespawn(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: PrefabInstances: undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: void OneTimeSetup(int, GameObject): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: NetworkObject GetInstanceToSpawn(NetworkManager): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: bool ValidatePersistedInstances(NetworkManager, int): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: .ctor(GameObject): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: void Initialize(NetworkManager): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: bool ValidateInstanceSpawnCount(int): XML is not well-formed: Expected an end tag for element 'summary'", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: void DeregisterHandler(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: NetworkObject GetInstance(): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: NetworkObject Instantiate(ulong, Vector3, Quaternion): undocumented", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: void Destroy(NetworkObject): undocumented", "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: NumberOfClients: undocumented", "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: .ctor(HostOrServer): undocumented", "Unity.Netcode.RuntimeTests.NetworkVariableCollectionsTests: bool OnSetVerboseDebug(): undocumented", @@ -949,14 +746,6 @@ "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void OnServerListValuesChanged(HashSet, HashSet): undocumented", "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void OnOwnerListValuesChanged(HashSet, HashSet): undocumented", "Unity.Netcode.RuntimeTests.IHashSetTestHelperBase: void ResetTrackedChanges(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: OnValueChanged: undocumented", - "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper: .ctor(NetworkVariableBase): undocumented", - "Unity.Netcode.TestHelpers.Runtime.NetworkVariableHelper.OnMyValueChangedDelegateHandler: undocumented", - "Unity.Netcode.TestHelpers.Runtime.NetworkVariableBaseHelper: .ctor(NetworkVariableBase): undocumented", - "Unity.Netcode.NetworkVariableSerialization: void WriteDelta(FastBufferWriter, ref T, ref T): missing ", - "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", - "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", - "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", "Unity.Netcode.RuntimeTests.ByteEnum: undocumented", "Unity.Netcode.RuntimeTests.ByteEnum: A: undocumented", "Unity.Netcode.RuntimeTests.ByteEnum: B: undocumented", @@ -989,85 +778,9 @@ "Unity.Netcode.RuntimeTests.ULongEnum: A: undocumented", "Unity.Netcode.RuntimeTests.ULongEnum: B: undocumented", "Unity.Netcode.RuntimeTests.ULongEnum: C: undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnNetworkSpawn(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void RegisterAndLabelNetworkObject(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void DeRegisterNetworkObject(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnLostOwnership(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnGainedOwnership(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnNetworkDespawn(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnDestroy(): undocumented", - "Unity.Netcode.RpcAttribute: RequireOwnership: undocumented", - "Unity.Netcode.RpcAttribute: DeferLocal: undocumented", - "Unity.Netcode.RpcAttribute: AllowTargetOverride: undocumented", - "Unity.Netcode.RpcAttribute: .ctor(SendTo): undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: Delivery: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: RequireOwnership: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: DeferLocal: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: AllowTargetOverride: undocumented", - "Unity.Netcode.ServerRpcAttribute: RequireOwnership: undocumented", - "Unity.Netcode.ServerRpcAttribute: .ctor(): undocumented", - "Unity.Netcode.ClientRpcAttribute: .ctor(): undocumented", - "Unity.Netcode.LocalDeferMode: undocumented", - "Unity.Netcode.LocalDeferMode: Default: undocumented", - "Unity.Netcode.LocalDeferMode: Defer: undocumented", - "Unity.Netcode.LocalDeferMode: SendImmediate: undocumented", - "Unity.Netcode.RpcSendParams: Target: undocumented", - "Unity.Netcode.RpcSendParams: LocalDeferMode: undocumented", - "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(BaseRpcTarget): undocumented", - "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(LocalDeferMode): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcSendParams): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(BaseRpcTarget): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(LocalDeferMode): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcReceiveParams): undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: NumberOfClients: undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: .ctor(HostOrServer): undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: IEnumerator OnSetup(): undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: void OnCreatePlayerPrefab(): undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting: IEnumerator ProxyDoesNotInvokeOnSender(): undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: ReceivedRpc: undocumented", - "Unity.Netcode.RuntimeTests.RpcProxyMessageTesting.RpcProxyText: void SendToEveryOneButMe(): undocumented", - "Unity.Netcode.RpcTarget: void Dispose(): undocumented", - "Unity.Netcode.RpcTarget: BaseRpcTarget Group(T, RpcTargetUse): missing ", - "Unity.Netcode.RpcTarget: BaseRpcTarget Not(T, RpcTargetUse): missing ", - "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: undocumented", - "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: OnServerRpcAction: undocumented", - "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: OnClientRpcAction: undocumented", - "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: void MyServerRpc(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.Metrics.RpcTestComponent: void MyClientRpc(ClientRpcParams): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: k_DefaultTimeOutWaitPeriod: undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: m_IsStarted: undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: TimedOut: undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: float GetTimeElapsed(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void OnStart(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void Start(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void OnStop(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: void Stop(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: bool OnHasTimedOut(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: bool HasTimedOut(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutHelper: .ctor(float): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: int GetFrameCount(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: void OnStop(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: bool OnHasTimedOut(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: void OnStart(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.TimeoutFrameCountHelper: .ctor(float, uint): undocumented", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): missing ", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeSerializer_Dictionary(): missing ", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): missing ", - "Unity.Netcode.NetworkVariableSerializationTypedInitializers: void InitializeEqualityChecker_Dictionary(): missing ", - "Unity.Netcode.Transports.UTP.UnityTransport: m_Driver: undocumented", - "Unity.Netcode.Transports.UTP.UnityTransport: NetworkConnection Connect(NetworkEndPoint): undocumented", - "Unity.Netcode.Transports.UTP.UnityTransport: NetworkTopologyTypes OnCurrentTopology(): undocumented", - "Unity.Netcode.Transports.UTP.UnityTransport.ConnectionAddressData: IsIpv6: undocumented", - "Unity.Netcode.UserNetworkVariableSerialization.WriteDeltaDelegate: missing ", - "Unity.Netcode.UserNetworkVariableSerialization.DuplicateValueDelegate: missing " + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: bool ValidateInstanceSpawnCount(int): missing ", + "Unity.Netcode.RuntimeTests.NetworkVariableBaseInitializesWhenPersisted.PrefabInstanceHandler: bool ValidateInstanceSpawnCount(int): missing " ] } - }, - "extends": [ - "rme", - "supported" - ] + } } diff --git a/com.unity.netcode.gameobjects/pvpExceptions.json.meta b/pvpExceptions.json.meta similarity index 100% rename from com.unity.netcode.gameobjects/pvpExceptions.json.meta rename to pvpExceptions.json.meta From f4886521ca31e98f815995ac187895df62e12703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:24:06 +0100 Subject: [PATCH 176/236] chore: [2.X] Renovate updates integration (#3287) * Removal of unused global.json file * Updated dependencies suggested by Renovate * Added rule to not update major versions * Modified packages according to minimal supported editor * Corrected Renovate setup to respect editor compatibility * Downgraded com.unity.test-framework version due to CI failures with newer version * typo correction * Reverting package changes * Corrected renovate setup to not update the package or test projects --------- Co-authored-by: Noel Stephens --- .github/renovate.json5 | 20 +++++++++---------- .github/workflows/conventional-pr.yml | 2 +- Tools/CI/global.json | 7 ------- minimalproject/Packages/manifest.json | 5 +---- .../Packages/manifest.json | 2 +- 5 files changed, 12 insertions(+), 24 deletions(-) delete mode 100644 Tools/CI/global.json diff --git a/.github/renovate.json5 b/.github/renovate.json5 index acab47d6f0..1b96abd02f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -7,15 +7,19 @@ "local>Unity-Technologies/unity-renovate-config" ], "prConcurrentLimit": 100, - // Ignore commits produced by github actions workflows + // Ignore commits produced by GitHub actions workflows "gitIgnoredAuthors": ["githubaction@githubaction.com"], "ignorePaths": [ "**/node_modules/**", // Don't renovate files in special folders using ~ as suffix - "**/*~/**" + "**/*~/**", + "com.unity.netcode.gameobjects/**/*", + "Examples/**/*", + "testproject/**/*", + "minimalproject/**/*", + "testproject-tools-integration/**/*" ], "packageRules": [ - // Run unity-upm-project and unity-upm-package only on weekends to reduce PR noise // Also ensure dependencies won't be downgraded when they don't exist in the public repositories { @@ -27,13 +31,7 @@ "schedule": [ "every weekend" ], - "rollbackPrs": false - }, - - // Enable automerge for Bokken image updates - { - "matchDatasources": ["unity-bokken"], - "automerge": false, - }, + "rollbackPrs": false, + } ], } diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 6090fe2ab2..e5b2504dca 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -18,7 +18,7 @@ jobs: steps: - name: semantic-pull-request # Internal Unity mirror available at jesseo/action-semantic-pull-request, but actions from private repos aren't supported, so continue to use the public one below - uses: amannn/action-semantic-pull-request@b7a9a97cb10fa6e1ae02647e718798175f6b1f1d + uses: amannn/action-semantic-pull-request@40166f00814508ec3201fc8595b393d451c8cd80 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/Tools/CI/global.json b/Tools/CI/global.json deleted file mode 100644 index 3cb4f9622d..0000000000 --- a/Tools/CI/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "7.0.0", - "rollForward": "latestMinor", - "allowPrerelease": false - } -} diff --git a/minimalproject/Packages/manifest.json b/minimalproject/Packages/manifest.json index 9c4ac7a7ee..b01ba5d497 100644 --- a/minimalproject/Packages/manifest.json +++ b/minimalproject/Packages/manifest.json @@ -1,9 +1,6 @@ { "dependencies": { - "com.unity.ai.navigation": "2.0.5", - "com.unity.multiplayer.center": "1.0.0", - "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", - "com.unity.modules.accessibility": "1.0.0" + "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects" }, "testables": [ "com.unity.netcode.gameobjects" diff --git a/testproject-tools-integration/Packages/manifest.json b/testproject-tools-integration/Packages/manifest.json index 86bed9f664..d0841aedff 100644 --- a/testproject-tools-integration/Packages/manifest.json +++ b/testproject-tools-integration/Packages/manifest.json @@ -4,7 +4,7 @@ "com.unity.ai.navigation": "2.0.5", "com.unity.ide.rider": "3.0.31", "com.unity.multiplayer.center": "1.0.0", - "com.unity.multiplayer.tools": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#release/1.1.0", + "com.unity.multiplayer.tools": "2.2.3", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.test-framework": "1.4.6", "com.unity.test-framework.performance": "3.0.3", From 069a926d8ac680450021a88fba4d0f8c432ecd72 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 14 Feb 2025 15:30:52 -0600 Subject: [PATCH 177/236] chore: update code owners up port (#3294) * update removing work-flow from codeowners to reduce the complexity of maintaining repository. * update remove multiplayer from code owners to reduce complexity of maintenance. * update adding Emma as a code owner * Update CODEOWNERS reverting this change. --- .github/CODEOWNERS | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ac441af2c8..a7cd830adc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,15 +2,10 @@ # Order is important; the last matching pattern takes the most precedence. * @Unity-Technologies/multiplayer-sdk -Profiling/ @Unity-Technologies/multiplayer-tools -Metrics/ @Unity-Technologies/multiplayer-tools -/com.unity.netcode.gameobjects/Runtime/Transports/ @Unity-Technologies/multiplayer-workflows -/com.unity.netcode.gameobjects/Tests/Editor/Transports/ @Unity-Technologies/multiplayer-workflows -/com.unity.netcode.gameobjects/Tests/Runtime/Transports/ @Unity-Technologies/multiplayer-workflows -*.asmdef @chrispope @miniwolf -package.json @chrispope @miniwolf -AssemblyInfo.cs @chrispope @miniwolf -.editorconfig @chrispope @miniwolf -.gitignore @chrispope @miniwolf -.github/ @chrispope @miniwolf -.yamato/ @chrispope @miniwolf +*.asmdef @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM +package.json @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM +AssemblyInfo.cs @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM +.editorconfig @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM +.gitignore @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM +.github/ @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM +.yamato/ @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM From 711aa0247e5529811201315a9b3c1a80906d7137 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 14 Feb 2025 18:08:28 -0500 Subject: [PATCH 178/236] feat: Rework the DestroyObject path on the non-authority client (#3291) * feat: Rework the DestroyObject path on the non-authority client * Changelog.md * update Adding warning if the NetworkObject does not exist since we should not be receiving a message to destroy a NetworkObject if it is either already destroyed =or= the local client is destroying objects when it should not be. * update Changing Debug to NetworkLog --------- Co-authored-by: NoelStephensUnity --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Messages/DestroyObjectMessage.cs | 111 ++++++++++-------- .../Runtime/Spawning/NetworkSpawnManager.cs | 54 ++++++--- 3 files changed, 97 insertions(+), 69 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 17abe14225..d2572fcc89 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed DestroyObject flow on non-authority game clients. (#3291) - Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) - Fixed issue where the scene migration synchronization table was not cleaned up if the `GameObject` of a `NetworkObject` is destroyed before it should have been. (#3230) - Fixed issue where the scene migration synchronization table was not cleaned up upon `NetworkManager` shutting down. (#3230) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index cdcff74251..2db9b8226b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Runtime.CompilerServices; namespace Unity.Netcode { @@ -81,7 +82,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int reader.ReadValueSafe(out DestroyGameObject); - if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { // Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy)) @@ -95,80 +96,90 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; + networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject); - var networkObject = (NetworkObject)null; - if (!networkManager.DistributedAuthorityMode) + // The DAHost needs to forward despawn messages to the other clients + if (networkManager.DAHost) { - // If this NetworkObject does not exist on this instance then exit early - if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject)) - { - return; - } - } - else - { - networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject); - if (!networkManager.DAHost && networkObject == null) + HandleDAHostForwardMessage(context.SenderId, ref networkManager, networkObject); + + // DAHost adds the object to the queue only if it is not a targeted destroy, or it is and the target is the DAHost client. + if (networkObject && DeferredDespawnTick > 0 && (!IsTargetedDestroy || (IsTargetedDestroy && TargetClientId == 0))) { - // If this NetworkObject does not exist on this instance then exit early + HandleDeferredDespawn(ref networkManager, ref networkObject); return; } } - // DANGO-TODO: This is just a quick way to foward despawn messages to the remaining clients - if (networkManager.DistributedAuthorityMode && networkManager.DAHost) + + // If this NetworkObject does not exist on this instance then exit early + if (!networkObject) { - var message = new DestroyObjectMessage - { - NetworkObjectId = NetworkObjectId, - DestroyGameObject = DestroyGameObject, - IsDistributedAuthority = true, - IsTargetedDestroy = IsTargetedDestroy, - TargetClientId = TargetClientId, // Just always populate this value whether we write it or not - DeferredDespawnTick = DeferredDespawnTick, - }; - var ownerClientId = networkObject == null ? context.SenderId : networkObject.OwnerClientId; - var clientIds = networkObject == null ? networkManager.ConnectedClientsIds.ToList() : networkObject.Observers.ToList(); - - foreach (var clientId in clientIds) + if (networkManager.LogLevel <= LogLevel.Developer) { - if (clientId == networkManager.LocalClientId || clientId == ownerClientId) - { - continue; - } - networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + NetworkLog.LogWarning($"[{nameof(DestroyObjectMessage)}] Received destroy object message for NetworkObjectId ({NetworkObjectId}) on Client-{networkManager.LocalClientId}, but that {nameof(NetworkObject)} does not exist!"); } + return; } - // If we are deferring the despawn, then add it to the deferred despawn queue if (networkManager.DistributedAuthorityMode) { - if (DeferredDespawnTick > 0) + // If we are deferring the despawn, then add it to the deferred despawn queue + // If DAHost has reached this point, it is not valid to add to the queue + if (DeferredDespawnTick > 0 && !networkManager.DAHost) { - // Clients always add it to the queue while DAHost will only add it to the queue if it is not a targeted destroy or it is and the target is the - // DAHost client. - if (!networkManager.DAHost || (networkManager.DAHost && (!IsTargetedDestroy || (IsTargetedDestroy && TargetClientId == 0)))) - { - networkObject.DeferredDespawnTick = DeferredDespawnTick; - var hasCallback = networkObject.OnDeferredDespawnComplete != null; - networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback, DestroyGameObject); - return; - } + HandleDeferredDespawn(ref networkManager, ref networkObject); + return; } // If this is targeted and we are not the target, then just update our local observers for this object - if (IsTargetedDestroy && TargetClientId != networkManager.LocalClientId && networkObject != null) + if (IsTargetedDestroy && TargetClientId != networkManager.LocalClientId) { networkObject.Observers.Remove(TargetClientId); return; } } - if (networkObject != null) + // Otherwise just despawn the NetworkObject right now + networkManager.SpawnManager.OnDespawnNonAuthorityObject(networkObject); + networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize); + } + + /// + /// Handles forwarding the when acting as the DA Host + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void HandleDAHostForwardMessage(ulong senderId, ref NetworkManager networkManager, NetworkObject networkObject) + { + var message = new DestroyObjectMessage { - // Otherwise just despawn the NetworkObject right now - networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject); - networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize); + NetworkObjectId = NetworkObjectId, + DestroyGameObject = DestroyGameObject, + IsDistributedAuthority = true, + IsTargetedDestroy = IsTargetedDestroy, + TargetClientId = TargetClientId, // Just always populate this value whether we write it or not + DeferredDespawnTick = DeferredDespawnTick, + }; + var ownerClientId = networkObject == null ? senderId : networkObject.OwnerClientId; + var clientIds = networkObject == null ? networkManager.ConnectedClientsIds.ToList() : networkObject.Observers.ToList(); + + foreach (var clientId in clientIds) + { + if (clientId != networkManager.LocalClientId && clientId != ownerClientId) + { + networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); + } } } + + /// + /// Handles adding to the deferred despawn queue when the indicates a deferred despawn + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void HandleDeferredDespawn(ref NetworkManager networkManager, ref NetworkObject networkObject) + { + networkObject.DeferredDespawnTick = DeferredDespawnTick; + var hasCallback = networkObject.OnDeferredDespawnComplete != null; + networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 4807e3dfc0..aa277735c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using UnityEngine; @@ -1470,7 +1471,6 @@ internal void ServerSpawnSceneObjectsOnStartSweep() #else var networkObjects = UnityEngine.Object.FindObjectsOfType(); #endif - var isConnectedCMBService = NetworkManager.CMBServiceConnection; var networkObjectsToSpawn = new List(); for (int i = 0; i < networkObjects.Length; i++) { @@ -1510,15 +1510,30 @@ internal void ServerSpawnSceneObjectsOnStartSweep() networkObjectsToSpawn.Clear(); } + /// + /// 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) + { + 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); + } + internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false) { - if (NetworkManager == null) + if (!NetworkManager) { return; } // We have to do this check first as subsequent checks assume we can access NetworkObjectId. - if (networkObject == null) + if (!networkObject) { Debug.LogWarning($"Trying to destroy network object but it is null"); return; @@ -1632,8 +1647,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec { NetworkObjectId = networkObject.NetworkObjectId, DeferredDespawnTick = networkObject.DeferredDespawnTick, - // DANGO-TODO: Reconfigure Client-side object destruction on despawn - DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true, + DestroyGameObject = destroyGameObject, IsTargetedDestroy = false, IsDistributedAuthority = distributedAuthority, }; @@ -2006,7 +2020,6 @@ internal struct DeferredDespawnObject { public int TickToDespawn; public bool HasDeferredDespawnCheck; - public bool DestroyGameObject; public ulong NetworkObjectId; } @@ -2018,13 +2031,12 @@ 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, bool destroyGameObject) + internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck) { var deferredDespawnObject = new DeferredDespawnObject() { TickToDespawn = tickToDespawn, HasDeferredDespawnCheck = hasDeferredDespawnCheck, - DestroyGameObject = destroyGameObject, NetworkObjectId = networkObjectId, }; DeferredDespawnObjects.Add(deferredDespawnObject); @@ -2041,15 +2053,21 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) return; } var currentTick = serverTime.Tick; - var deferredCallbackCount = DeferredDespawnObjects.Count(); - for (int i = 0; i < deferredCallbackCount; i++) + var deferredDespawnCount = DeferredDespawnObjects.Count; + // Loop forward and to process user callbacks and update despawn ticks + for (int i = 0; i < deferredDespawnCount; i++) { var deferredObjectEntry = DeferredDespawnObjects[i]; if (!deferredObjectEntry.HasDeferredDespawnCheck) { continue; } - var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId]; + + if (!SpawnedObjects.TryGetValue(deferredObjectEntry.NetworkObjectId, out var networkObject)) + { + continue; + } + // Double check to make sure user did not remove the callback if (networkObject.OnDeferredDespawnComplete != null) { @@ -2074,7 +2092,7 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) } // Parse backwards so we can remove objects as we parse through them - for (int i = DeferredDespawnObjects.Count - 1; i >= 0; i--) + for (int i = deferredDespawnCount - 1; i >= 0; i--) { var deferredObjectEntry = DeferredDespawnObjects[i]; if (deferredObjectEntry.TickToDespawn >= currentTick) @@ -2082,15 +2100,13 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) continue; } - if (!SpawnedObjects.ContainsKey(deferredObjectEntry.NetworkObjectId)) + if (SpawnedObjects.TryGetValue(deferredObjectEntry.NetworkObjectId, out var networkObject)) { - DeferredDespawnObjects.Remove(deferredObjectEntry); - continue; + // Local instance despawns the instance + OnDespawnNonAuthorityObject(networkObject); } - var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId]; - // Local instance despawns the instance - OnDespawnObject(networkObject, deferredObjectEntry.DestroyGameObject); - DeferredDespawnObjects.Remove(deferredObjectEntry); + + DeferredDespawnObjects.RemoveAt(i); } } From 7b239f22ff8d7e5adea2bb95c127a3deb6a51dcc Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 19 Feb 2025 15:48:38 -0500 Subject: [PATCH 179/236] feat: Optimize DestroyObject message (#3304) * feat: Optimize destroy object message * CHANGELOG.md * Add summary to DestroyGameObject parameter --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Messages/DestroyObjectMessage.cs | 69 +++++++++++++------ 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d2572fcc89..cc82759db7 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -24,6 +24,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed the `DestroyObject` message to reduce the serialized message size and remove the unnecessary message field. (#3304) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) ## [2.2.0] - 2024-12-12 diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 2db9b8226b..f519a11827 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -5,11 +5,17 @@ namespace Unity.Netcode { internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy { - public int Version => 0; + private const int k_OptimizeDestroyObjectMessage = 1; + public int Version => k_OptimizeDestroyObjectMessage; 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; @@ -19,19 +25,21 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp internal bool IsDistributedAuthority; - internal const byte ClientTargetedDestroy = 0x01; + private const byte k_ClientTargetedDestroy = 0x01; + private const byte k_DeferredDespawn = 0x02; internal bool IsTargetedDestroy { - get - { - return GetFlag(ClientTargetedDestroy); - } + get => GetFlag(k_ClientTargetedDestroy); - set - { - SetFlag(value, ClientTargetedDestroy); - } + set => SetFlag(value, k_ClientTargetedDestroy); + } + + private bool IsDeferredDespawn + { + get => GetFlag(k_DeferredDespawn); + + set => SetFlag(value, k_DeferredDespawn); } private bool GetFlag(int flag) @@ -47,7 +55,11 @@ private void SetFlag(bool set, byte flag) public void Serialize(FastBufferWriter writer, int targetVersion) { + // Set deferred despawn flag + IsDeferredDespawn = DeferredDespawnTick > 0; + BytePacker.WriteValueBitPacked(writer, NetworkObjectId); + if (IsDistributedAuthority) { writer.WriteByteSafe(m_DestroyFlags); @@ -56,9 +68,17 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, TargetClientId); } - BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); + + if (targetVersion < k_OptimizeDestroyObjectMessage || IsDeferredDespawn) + { + BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); + } + } + + if (targetVersion < k_OptimizeDestroyObjectMessage) + { + writer.WriteValueSafe(DestroyGameObject); } - writer.WriteValueSafe(DestroyGameObject); } public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) @@ -77,18 +97,27 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int { ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId); } - ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); + + if (receivedMessageVersion < k_OptimizeDestroyObjectMessage || IsDeferredDespawn) + { + ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); + } } - reader.ReadValueSafe(out DestroyGameObject); + if (receivedMessageVersion < k_OptimizeDestroyObjectMessage) + { + reader.ReadValueSafe(out DestroyGameObject); + } - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) + if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { - // Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy - if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy)) - { - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name); - } + return true; + } + + // Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy + if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy)) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name); } return true; } From 94938249d1d8beb32d20bf29bf05e0344d107c2d Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 20 Feb 2025 18:04:26 -0500 Subject: [PATCH 180/236] fix: Incorrect clientId in OnClientConnectedCallback (#3312) * fix: Incorrect clientId in OnClientConnectedCallback * CHANGELOG.md --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Messages/ConnectionApprovedMessage.cs | 2 +- .../Tests/Runtime/Connection.meta | 3 + .../Connection/ClientConnectionTests.cs | 82 +++++++++++++++++++ .../Connection/ClientConnectionTests.cs.meta | 3 + 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Connection.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index cc82759db7..75f3dd780a 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) - Fixed DestroyObject flow on non-authority game clients. (#3291) - Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) - Fixed issue where the scene migration synchronization table was not cleaned up if the `GameObject` of a `NetworkObject` is destroyed before it should have been. (#3230) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 5005c1987c..4f9e0f5453 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -305,7 +305,7 @@ public void Handle(ref NetworkContext context) NetworkLog.LogInfo($"[Client-{OwnerClientId}][Scene Management Disabled] Synchronization complete!"); } // When scene management is disabled we notify after everything is synchronized - networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); + networkManager.ConnectionManager.InvokeOnClientConnectedCallback(OwnerClientId); // For convenience, notify all NetworkBehaviours that synchronization is complete. networkManager.SpawnManager.NotifyNetworkObjectsSynchronized(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection.meta new file mode 100644 index 0000000000..e8a0528d8a --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6a87f9e5685b4895bd5a7936e6ac567f +timeCreated: 1740072310 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs new file mode 100644 index 0000000000..d4c1550b8d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs @@ -0,0 +1,82 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(SceneManagementState.SceneManagementEnabled, NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(SceneManagementState.SceneManagementDisabled, NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(SceneManagementState.SceneManagementEnabled, NetworkTopologyTypes.ClientServer)] + [TestFixture(SceneManagementState.SceneManagementDisabled, NetworkTopologyTypes.ClientServer)] + internal class ClientConnectionTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 3; + private readonly bool m_SceneManagementEnabled; + private HashSet m_ServerCallbackCalled = new HashSet(); + private HashSet m_ClientCallbackCalled = new HashSet(); + + public ClientConnectionTests(SceneManagementState sceneManagementState, NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) + { + m_SceneManagementEnabled = sceneManagementState == SceneManagementState.SceneManagementEnabled; + } + + protected override void OnServerAndClientsCreated() + { + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_SceneManagementEnabled; + m_ServerNetworkManager.OnClientConnectedCallback += Server_OnClientConnectedCallback; + + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.EnableSceneManagement = m_SceneManagementEnabled; + client.OnClientConnectedCallback += Client_OnClientConnectedCallback; + } + + base.OnServerAndClientsCreated(); + } + + [UnityTest] + public IEnumerator VerifyOnClientConnectedCallback() + { + yield return WaitForConditionOrTimeOut(AllCallbacksCalled); + AssertOnTimeout("Timed out waiting for all clients to be connected!"); + + // The client callbacks should have been called once per client (called once on self) + Assert.True(m_ClientCallbackCalled.Count == NumberOfClients); + + // The server callback should be called for self, and then once per client + Assert.True(m_ServerCallbackCalled.Count == 1 + NumberOfClients); + } + + private void Server_OnClientConnectedCallback(ulong clientId) + { + if (!m_ServerCallbackCalled.Add(clientId)) + { + Assert.Fail($"Client already connected: {clientId}"); + } + } + + private void Client_OnClientConnectedCallback(ulong clientId) + { + if (!m_ClientCallbackCalled.Add(clientId)) + { + Assert.Fail($"Client already connected: {clientId}"); + } + } + + private bool AllCallbacksCalled() + { + foreach (var client in m_ClientNetworkManagers) + { + if (!m_ClientCallbackCalled.Contains(client.LocalClientId) || !m_ServerCallbackCalled.Contains(client.LocalClientId)) + { + return false; + } + } + + return m_ServerCallbackCalled.Contains(m_ServerNetworkManager.LocalClientId); + } + } +} + diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs.meta new file mode 100644 index 0000000000..a52e7c6ccd --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ClientConnectionTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c5c3b4494e2946dc967cc70ffea4d850 +timeCreated: 1740072340 \ No newline at end of file From 534f74e28c40d68ac1c4405c42f03aac60cf017e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 20 Feb 2025 19:05:26 -0600 Subject: [PATCH 181/236] fix: selecting everything selects session owner only (#3305) * fix This fixes the issue with setting flags on NetworkObjects and the default Everything/All flag causing session owner to be grouped with the rest of the flags. Now all will initially select all flags but the session owner but if the all flag is selected again it toggles to just the SessionOwner flag. This also assures that all root in-scene placed NetworkObjects have the Distributable or SessionOwner flag set (if neither then it defaults to Distributable). * fix Fixing some logic that would only skip over children if it was logging a message. * update adding changelog entries. * style adding API comments * style Adding one for "None". * update Cleaning up some of the script. * style adding comments --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ .../Editor/NetworkObjectEditor.cs | 35 +++++++++++++++++-- .../Runtime/Core/NetworkObject.cs | 29 +++++++++++++++ .../Runtime/Spawning/NetworkSpawnManager.cs | 7 ++-- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 75f3dd780a..6922d1eeda 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) +- Fixed issue where the `NetworkObject.Ownership` custom editor did not take the default "Everything" flag into consideration. (#3305) - Fixed DestroyObject flow on non-authority game clients. (#3291) - Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) - Fixed issue where the scene migration synchronization table was not cleaned up if the `GameObject` of a `NetworkObject` is destroyed before it should have been. (#3230) @@ -25,6 +26,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed root in-scene placed `NetworkObject` instances now will always have either the `Distributable` permission set unless the `SessionOwner` permission is set. (#3305) - Changed the `DestroyObject` message to reduce the serialized message size and remove the unnecessary message field. (#3304) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 0860fd9c92..04e0d1f698 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -14,6 +14,9 @@ namespace Unity.Netcode.Editor [CanEditMultipleObjects] public class NetworkObjectEditor : UnityEditor.Editor { + private const NetworkObject.OwnershipStatus k_AllOwnershipFlags = NetworkObject.OwnershipStatus.RequestRequired | NetworkObject.OwnershipStatus.Transferable | NetworkObject.OwnershipStatus.Distributable; + private const int k_SessionOwnerFlagAsInt = (int)NetworkObject.OwnershipStatus.SessionOwner; + private bool m_Initialized; private NetworkObject m_NetworkObject; private bool m_ShowObservers; @@ -114,10 +117,38 @@ public override void OnInspectorGUI() { EditorGUI.BeginChangeCheck(); serializedObject.UpdateIfRequiredOrScript(); + + // Get the current ownership property and precalculate values in order to handle + // the exclusion or inclusion of "all" or just the session owner flags. + var ownershipProperty = serializedObject.FindProperty(nameof(NetworkObject.Ownership)); + var previousOwnership = (NetworkObject.OwnershipStatus)ownershipProperty.intValue; + var hadAll = previousOwnership == k_AllOwnershipFlags; + var hadSessionOwner = ownershipProperty.intValue == k_SessionOwnerFlagAsInt; + DrawPropertiesExcluding(serializedObject, k_HiddenFields); - if (m_NetworkObject.IsOwnershipSessionOwner) + + // If the ownership flags were changed + var currentOwnership = (NetworkObject.OwnershipStatus)ownershipProperty.intValue; + if (currentOwnership != previousOwnership) { - m_NetworkObject.Ownership = NetworkObject.OwnershipStatus.SessionOwner; + // Determine if we need to handle setting or removing the session owner flag specifically + // when a user selects the "All" enum flag value. + var hasSessionOwner = currentOwnership.HasFlag(NetworkObject.OwnershipStatus.SessionOwner); + if (hasSessionOwner) + { + if (ownershipProperty.intValue == -1 && !hadAll) + { + ownershipProperty.intValue = (int)k_AllOwnershipFlags; + } + else if ((hadAll && !hadSessionOwner) || (!hadAll && !hadSessionOwner)) + { + ownershipProperty.intValue = k_SessionOwnerFlagAsInt; + } + else if (hadSessionOwner && hasSessionOwner) + { + ownershipProperty.intValue &= ~k_SessionOwnerFlagAsInt; + } + } } serializedObject.ApplyModifiedProperties(); EditorGUI.EndChangeCheck(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 2b0900a166..a14bd79a2b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -322,6 +322,15 @@ private void CheckForInScenePlaced() // Default scene migration synchronization to false for in-scene placed NetworkObjects SceneMigrationSynchronization = false; + + // Root In-scene placed NetworkObjects have to either have the SessionOwner or Distributable permission flag set. + if (transform.parent == null) + { + if (!Ownership.HasFlag(OwnershipStatus.SessionOwner) && !Ownership.HasFlag(OwnershipStatus.Distributable)) + { + Ownership |= OwnershipStatus.Distributable; + } + } } } #endif // UNITY_EDITOR @@ -493,16 +502,36 @@ public void DeferDespawn(int tickOffset, bool destroy = true) /// : When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked). /// : When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). /// : When set, only the current session owner may have ownership over this object. + /// : Used within the inspector view only. When selected it will set the Distributable, Transferable, and RequestRequired flags or if those flags are already set it will select the SessionOwner flag by itself. /// // Ranges from 1 to 8 bits [Flags] public enum OwnershipStatus { + /// + /// When set, this instance will have no permissions (i.e. cannot distribute, transfer, etc). + /// None = 0, + /// + /// When set, this instance will be automatically redistributed when a client joins (if not locked or no request is pending) or leaves. + /// Distributable = 1 << 0, + /// + /// When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked). + /// Transferable = 1 << 1, + /// + /// When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). + /// RequestRequired = 1 << 2, + /// + /// When set, only the current session owner may have ownership over this object. + /// SessionOwner = 1 << 3, + /// + /// Used within the inspector view only. When selected it will set the Distributable, Transferable, and RequestRequired flags or if those flags are already set it will select the SessionOwner flag by itself. + /// + All = ~0, } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index aa277735c3..9f219e4c35 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1967,9 +1967,12 @@ internal void DistributeNetworkObjects(ulong clientId) { continue; } - if ((!child.IsOwnershipDistributable || !child.IsOwnershipTransferable) && NetworkManager.LogLevel == LogLevel.Developer) + if (!child.IsOwnershipDistributable || !child.IsOwnershipTransferable) { - NetworkLog.LogWarning($"Sibling {child.name} of root parent {ownerList.Value[i].name} is neither transferrable or distributable! Object distribution skipped and could lead to a potentially un-owned or owner-mismatched {nameof(NetworkObject)}!"); + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"Sibling {child.name} of root parent {ownerList.Value[i].name} is neither transferrable or distributable! Object distribution skipped and could lead to a potentially un-owned or owner-mismatched {nameof(NetworkObject)}!"); + } continue; } // Transfer ownership of all distributable =or= transferrable children with the same owner to the same client to preserve the sibling ownership tree. From 65cac82bf758dffd0d5afa858e564ea29bd5554a Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:33:08 +0100 Subject: [PATCH 182/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.38 (develop-2.0.0) (#3311) Update of Wrench to 10.0.38 and recipe regeneration --- .yamato/wrench/api-validation-jobs.yml | 6 +-- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 56 ++++++++++---------- .yamato/wrench/promotion-jobs.yml | 12 ++--- .yamato/wrench/recipe-regeneration.yml | 6 +-- .yamato/wrench/validation-jobs.yml | 72 +++++++++++++------------- .yamato/wrench/wrench_config.json | 4 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 8 files changed, 80 insertions(+), 80 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index e657387536..9f518da240 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -9,7 +9,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -47,11 +47,11 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 triggers: expression: push.branch match "^release/.*" cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index be85fdab2b..6e87bc64cf 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 2b2d966538..c834cdd555 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -19,7 +19,7 @@ all_preview_apv_jobs: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -29,7 +29,7 @@ preview_apv_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -70,10 +70,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -83,7 +83,7 @@ preview_apv_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -124,10 +124,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -138,7 +138,7 @@ preview_apv_-_6000_0_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -179,10 +179,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -192,7 +192,7 @@ preview_apv_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -233,10 +233,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -246,7 +246,7 @@ preview_apv_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -287,10 +287,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -301,7 +301,7 @@ preview_apv_-_6000_1_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -342,10 +342,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -355,7 +355,7 @@ preview_apv_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -396,10 +396,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -409,7 +409,7 @@ preview_apv_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -450,10 +450,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -464,7 +464,7 @@ preview_apv_-_6000_2_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -505,8 +505,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index ff7853916d..d4230d9ec9 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -9,7 +9,7 @@ publish_dry_run_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -100,13 +100,13 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 triggers: expression: push.branch match "^release/.*" cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -116,7 +116,7 @@ publish_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -207,8 +207,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index 8494f3931c..e0fe34e545 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -9,14 +9,14 @@ test_-_wrench_jobs_up_to_date: type: Unity::VM flavor: b1.large commands: - - command: dotnet run --project Tools/CI/NGO.Cookbook.csproj + - command: dotnet run --project Tools\CI\NGO.Cookbook.csproj - command: |- if [ -n "$(git status --porcelain)" ]; then git status echo "Your repo is not clean - diff output:" git diff echo "You must run recipe generation after updating recipes to update the generated YAML!" - echo "Run 'dotnet run --project Tools/CI/NGO.Cookbook.csproj' from the root of your repository to regenerate all job definitions created by wrench." + echo "Run 'dotnet run --project Tools\CI\NGO.Cookbook.csproj' from the root of your repository to regenerate all job definitions created by wrench." exit 1 fi variables: @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 7669869309..2f0430cec7 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -9,7 +9,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -29,7 +29,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -75,7 +75,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -95,7 +95,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -141,7 +141,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -161,7 +161,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -207,7 +207,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -227,7 +227,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -273,7 +273,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -293,7 +293,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -339,7 +339,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -359,7 +359,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -405,7 +405,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -425,7 +425,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -471,7 +471,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -491,7 +491,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects @@ -537,7 +537,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-56_6391218b3c9a0a6d36cd0b122ff5dd48d68381a518cd09da6f7279c68a7c7962.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -557,7 +557,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - command: upm-pvp require "rme PVP-160-1 supported" --results upm-ci~/pvp --exemptions upm-ci~/pvp/failures.json timeout: 10 retries: 0 - - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" + - command: UnifiedTestRunner.exe --testproject=test-netcode.gameobjects --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts --suite=Editor --suite=Playmode "--ff={ops.upmpvpevidence.enable=true}" timeout: 40 retries: 1 after: @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.37.0 + UPMPVP_CONTEXT_WRENCH: 0.10.38.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.37.0 + Wrench: 0.10.38.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 8826b74083..d8197acdfe 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.37.0", + "wrench_version": "0.10.38.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", - "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" + "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index 47b1bfe16b..c9f598a055 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From fc03f9496b527127e41c4de17b3f2989b1a84951 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 21 Feb 2025 11:40:15 -0500 Subject: [PATCH 183/236] ci: [2.X] Turn off title validation for single commit PRs (#3314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 2.x When the semantic commit action was added into the branch, the default squash commit for a pr with only a single commit was the commit name and not the PR title. Since then, Github has [added a feature to allow configuration of the default](https://github.com/orgs/community/discussions/16271) and we have that setting set in our repo. This PR turns off the validation of commits as the check is not valid anymore. Co-authored-by: Michał Chrobot <124174716+michalChrobot@users.noreply.github.com> --- .github/workflows/conventional-pr.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index e5b2504dca..9b86504074 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -1,6 +1,6 @@ name: Conventional PR -# Controls when the action will run. +# Controls when the action will run. on: pull_request: branches: @@ -18,7 +18,7 @@ jobs: steps: - name: semantic-pull-request # Internal Unity mirror available at jesseo/action-semantic-pull-request, but actions from private repos aren't supported, so continue to use the public one below - uses: amannn/action-semantic-pull-request@40166f00814508ec3201fc8595b393d451c8cd80 + uses: amannn/action-semantic-pull-request@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -35,4 +35,4 @@ jobs: # For work-in-progress PRs you can typically use draft pull requests from Github. However, private repositories on the free plan don't have this option and therefore this action allows you to opt-in to using the special '[WIP]' prefix to indicate this state. This will avoid the validation of the PR title and the pull request checks remain pending. Note that a second check will be reported if this is enabled. #wip: # optional # When using "Squash and merge" on a PR with only one commit, GitHub will suggest using that commit message instead of the PR title for the merge commit, and it's easy to commit this by mistake. Enable this option to also validate the commit message for one commit PRs. - validateSingleCommit: true # optional + # validateSingleCommit: true # optional From b47bc65c7fe2646087da52e9b41447e9148b09f5 Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 12:57:23 +0100 Subject: [PATCH 184/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.39 (develop-2.0.0) (#3316) Updated wrench and regenerated recipes --- .yamato/wrench/api-validation-jobs.yml | 11 +++---- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 41 ++++++++++++-------------- .yamato/wrench/promotion-jobs.yml | 11 +++---- .yamato/wrench/publish-trigger.yml | 13 ++++++++ .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 36 +++++++++++----------- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 9 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 .yamato/wrench/publish-trigger.yml diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 9f518da240..d3d8bb78e9 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -1,5 +1,9 @@ # Auto-generated by Recipe Engine, do not modify manually. # This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb +all_api_validation_jobs: + name: All API Validation Jobs + dependencies: + - path: .yamato/wrench/api-validation-jobs.yml#api_validation_-_netcode_gameobjects_-_6000_0_-_windows # upm-ci validation tests for API Validation - netcode.gameobjects - 6000.0 - windows (6000.0 - Windows). api_validation_-_netcode_gameobjects_-_6000_0_-_windows: @@ -47,11 +51,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 - triggers: - expression: push.branch match "^release/.*" - cancel_old_ci: true + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 6e87bc64cf..34471effd9 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index c834cdd555..ee5c303553 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -14,12 +14,9 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_macos - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_ubuntu - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows - triggers: - expression: push.branch match "^release/.*" - cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -70,10 +67,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -124,10 +121,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -179,10 +176,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -233,10 +230,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -287,10 +284,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -342,10 +339,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -396,10 +393,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -450,10 +447,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -505,8 +502,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index d4230d9ec9..81f9fe8cce 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -100,13 +100,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 - triggers: - expression: push.branch match "^release/.*" - cancel_old_ci: true + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -207,8 +204,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 diff --git a/.yamato/wrench/publish-trigger.yml b/.yamato/wrench/publish-trigger.yml new file mode 100644 index 0000000000..e71b6ed8cd --- /dev/null +++ b/.yamato/wrench/publish-trigger.yml @@ -0,0 +1,13 @@ +# Auto-generated by Recipe Engine, do not modify manually. +# This job is generated by the wrench recipe engine module, see find the docs here: http://Go/ii2fb +all_promotion_related_jobs_promotiontrigger: + name: All Promotion Related Jobs PromotionTrigger + dependencies: + - path: .yamato/wrench/api-validation-jobs.yml#all_api_validation_jobs + - path: .yamato/wrench/preview-a-p-v.yml#all_preview_apv_jobs + - path: .yamato/wrench/promotion-jobs.yml#publish_dry_run_netcode_gameobjects + - path: .yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects + triggers: + expression: push.branch match "^release/.*" + cancel_old_ci: true + diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index e0fe34e545..e1cfad39db 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 2f0430cec7..cad9547e17 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.38.0 + UPMPVP_CONTEXT_WRENCH: 0.10.39.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.38.0 + Wrench: 0.10.39.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index d8197acdfe..c437896ae6 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.38.0", + "wrench_version": "0.10.39.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index c9f598a055..17a0aec1e8 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From 6ecfcc1a6d657e60b1d59b3080b34dadfb8ed5e2 Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 09:22:55 +0100 Subject: [PATCH 185/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.40 (develop-2.0.0) (#3318) Updated wrench to 0.10.40 and regenerated recipes --- .yamato/wrench/api-validation-jobs.yml | 4 +-- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 38 +++++++++++++------------- .yamato/wrench/promotion-jobs.yml | 8 +++--- .yamato/wrench/publish-trigger.yml | 1 - .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 36 ++++++++++++------------ .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 9 files changed, 47 insertions(+), 48 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index d3d8bb78e9..6b640688c9 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -51,8 +51,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 34471effd9..9ea7ac892f 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index ee5c303553..46274fc09f 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,7 +16,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -67,10 +67,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -121,10 +121,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -176,10 +176,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -230,10 +230,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -284,10 +284,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -339,10 +339,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -393,10 +393,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -447,10 +447,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -502,8 +502,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 81f9fe8cce..12b2d85339 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -100,10 +100,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -204,8 +204,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 diff --git a/.yamato/wrench/publish-trigger.yml b/.yamato/wrench/publish-trigger.yml index e71b6ed8cd..bd7db9bc73 100644 --- a/.yamato/wrench/publish-trigger.yml +++ b/.yamato/wrench/publish-trigger.yml @@ -6,7 +6,6 @@ all_promotion_related_jobs_promotiontrigger: - path: .yamato/wrench/api-validation-jobs.yml#all_api_validation_jobs - path: .yamato/wrench/preview-a-p-v.yml#all_preview_apv_jobs - path: .yamato/wrench/promotion-jobs.yml#publish_dry_run_netcode_gameobjects - - path: .yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects triggers: expression: push.branch match "^release/.*" cancel_old_ci: true diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index e1cfad39db..299ecec419 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index cad9547e17..ac84c7a8dd 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.39.0 + UPMPVP_CONTEXT_WRENCH: 0.10.40.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.39.0 + Wrench: 0.10.40.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index c437896ae6..90cd0ef491 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.39.0", + "wrench_version": "0.10.40.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index 17a0aec1e8..c6f2e29c41 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From 84c06c7dd77b66c6b08088ad9d77bb30bcf87020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Mon, 24 Feb 2025 21:51:47 +0100 Subject: [PATCH 186/236] ci: [2.X] CI failures fixes (#3302) This PR addresses some test failures in new CI definition (present on develop-2.0.0 branch) 1. **Code Coverage test failure** --> This test was failing because packed package couldn't be find. This steams from the fact that the path was wrongly pointing to different place because package pack job was modified to fix this. 2. **minimalproject standards failure** --> We were focusing before on making sure that testproject is conforming to standards (which also means that NGO package conforms to standards) but minimalproject has few errors which we aim to fix in this PR 3. **testproject-tools-integration standards failure** --> We were focusing before on making sure that testproject is conforming to standards (which also means that NGO package conforms to standards) but testproject-tools-integration has few errors which we aim to fix in this PR --------- Co-authored-by: NoelStephensUnity --- .yamato/code-coverage.yml | 5 +- .yamato/package-pack.yml | 24 ++++++ .../Editor/HiddenScriptEditor.cs | 2 + .../Runtime/Components/NetworkTransform.cs | 2 +- .../Runtime/Metrics/WaitForMetricValues.cs | 2 +- .../Runtime/Metrics/MetricsDispatchTests.cs | 2 +- .../Metrics/NetworkObjectMetricsTests.cs | 3 +- .../Metrics/NetworkVariableMetricsTests.cs | 2 +- .../Metrics/OwnershipChangeMetricsTests.cs | 4 +- .../Runtime/Metrics/PacketLossMetricsTests.cs | 1 - .../Runtime/Metrics/PacketMetricsTests.cs | 2 +- .../Tests/Runtime/Metrics/RpcMetricsTests.cs | 2 +- .../Tests/Runtime/Metrics/RttMetricsTests.cs | 2 +- .../Runtime/Metrics/ServerLogsMetricTests.cs | 2 +- minimalproject/Packages/manifest.json | 1 + .../Assets/Tests/Runtime/SceneEventTests.cs | 2 +- .../Packages/manifest.json | 4 +- .../Packages/packages-lock.json | 75 ++++++++++++------- 18 files changed, 94 insertions(+), 43 deletions(-) diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index 0e2d499f8d..c0aa7e59fd 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -16,12 +16,13 @@ code_coverage_{{ platform.name }}_{{ editor }}: commands: - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait - - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %} upm-ci package test -u {{ editor }} --package-path com.unity.netcode.gameobjects --enable-code-coverage --code-coverage-options 'generateAdditionalMetrics;generateHtmlReport;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime' --extra-utr-arg="--extra-editor-arg=--burst-disable-compilation --extra-editor-arg=-testCategory --extra-editor-arg=!Performance --timeout=1800 --reruncount=1 --clean-library-on-rerun" + - DISPLAY=:0 upm-ci package test -u {{ editor }} --package-path com.unity.netcode.gameobjects --enable-code-coverage --code-coverage-options 'generateAdditionalMetrics;generateHtmlReport;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime' --extra-utr-arg="--extra-editor-arg=--burst-disable-compilation --extra-editor-arg=testCategory --extra-editor-arg=!Performance --timeout=1800 --reruncount=1 --clean-library-on-rerun" artifacts: logs: paths: - "upm-ci~/test-results/**/*" + - "upm-ci~/test-results/CoverageResults/**/*" dependencies: - - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} + - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }}_upmCI {% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/package-pack.yml b/.yamato/package-pack.yml index a9ab902847..492c08ef78 100644 --- a/.yamato/package-pack.yml +++ b/.yamato/package-pack.yml @@ -25,4 +25,28 @@ package_pack_-_ngo_{{ platform.name }}: packages: paths: - "upm-ci~/**" +{% endfor -%} + + +# This is in essence the same job as the one above with the difference that upm-ci is used instead of upm-pvp +# The reason for using it is that I had some problems with Code Coverage which in its current form uses upm-ci but if we would use the other pack job (the one above) we would need to use upm-pvp +# I had some problems with getting it to work so as temporary solution I created this pack job which is used ONLY as a dependency of Code Coverage job (other jobs use the above definition of pack job) +# TODO: remove this job and utilize the above one for Code Coverage job. This is tracked in MTT-11383 +{% for platform in test_platforms.default -%} +{% for project in projects.default -%} +package_pack_-_ngo_{{ platform.name }}_upmCI: + name: Package Pack (legacy upm-ci) - NGO [{{ platform.name }}] + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor }} + timeout: 0.25 + commands: + - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - upm-ci project pack --project-path {{ project.path }} + artifacts: + packages: + paths: + - "upm-ci~/packages/**/*" +{% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs index 8e885ead97..f4a77abc4c 100644 --- a/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/HiddenScriptEditor.cs @@ -1,4 +1,6 @@ +#if COM_UNITY_MODULES_ANIMATION || COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D using Unity.Netcode.Components; +#endif #if UNITY_UNET_PRESENT using Unity.Netcode.Transports.UNET; #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index abbf1ddeb0..7b2126b39d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1793,7 +1793,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra #else var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; - var positionThreshold = Vector3.one * PositionThreshold; + var positionThreshold = Vector3.one * PositionThreshold; var rotationThreshold = Vector3.one * RotAngleThreshold; #endif var rotAngles = rotation.eulerAngles; diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForMetricValues.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForMetricValues.cs index 01013851a4..10e8c6b8c5 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForMetricValues.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/Metrics/WaitForMetricValues.cs @@ -21,7 +21,7 @@ public WaitForMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo d dispatcher.RegisterObserver(this); } - abstract public void Observe(MetricCollection collection); + public abstract void Observe(MetricCollection collection); public void AssertMetricValuesHaveNotBeenFound() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs index e8a268cf91..6d1eb91789 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MetricsDispatchTests.cs @@ -3,8 +3,8 @@ using System.Collections; using NUnit.Framework; using Unity.Multiplayer.Tools.NetStats; -using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs index dce18644f3..31abcadd50 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs @@ -4,11 +4,10 @@ using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; +using Unity.Netcode.TestHelpers.Runtime.Metrics; using UnityEngine; using UnityEngine.TestTools; -using Unity.Netcode.TestHelpers.Runtime.Metrics; - namespace Unity.Netcode.RuntimeTests.Metrics { internal class NetworkObjectMetricsTests : SingleClientMetricTestBase diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs index 640cee4615..11f38cea1b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs @@ -3,8 +3,8 @@ using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; -using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime.Metrics; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index a3fcf30b3f..cc08dcd5b7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -4,10 +4,10 @@ using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; -using UnityEngine; -using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime.Metrics; +using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index 91c242b5fd..f02c0b2467 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -1,6 +1,5 @@ #if MULTIPLAYER_TOOLS #if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - using System.Collections; using NUnit.Framework; using Unity.Collections; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs index 90707de0b8..b6bfd71bf9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketMetricsTests.cs @@ -4,8 +4,8 @@ using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; -using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime.Metrics; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs index b2b186a6f6..f68d502597 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RpcMetricsTests.cs @@ -4,8 +4,8 @@ using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; -using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime.Metrics; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RttMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RttMetricsTests.cs index 2c77c66f3a..3a1a2ca2d4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RttMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/RttMetricsTests.cs @@ -7,9 +7,9 @@ using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; -using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime.Metrics; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs index 4fbd75fe36..758d7aa114 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/ServerLogsMetricTests.cs @@ -5,8 +5,8 @@ using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; -using UnityEngine.TestTools; using Unity.Netcode.TestHelpers.Runtime.Metrics; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/minimalproject/Packages/manifest.json b/minimalproject/Packages/manifest.json index b01ba5d497..c7c27fc5e9 100644 --- a/minimalproject/Packages/manifest.json +++ b/minimalproject/Packages/manifest.json @@ -1,5 +1,6 @@ { "dependencies": { + "com.unity.ide.rider": "3.0.34", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects" }, "testables": [ diff --git a/testproject-tools-integration/Assets/Tests/Runtime/SceneEventTests.cs b/testproject-tools-integration/Assets/Tests/Runtime/SceneEventTests.cs index c5d5a17812..eccaf57490 100644 --- a/testproject-tools-integration/Assets/Tests/Runtime/SceneEventTests.cs +++ b/testproject-tools-integration/Assets/Tests/Runtime/SceneEventTests.cs @@ -5,10 +5,10 @@ using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime.Metrics; using UnityEngine.SceneManagement; using UnityEngine.TestTools; -using Unity.Netcode.TestHelpers.Runtime; using SceneEventType = Unity.Netcode.SceneEventType; namespace TestProject.ToolsIntegration.RuntimeTests diff --git a/testproject-tools-integration/Packages/manifest.json b/testproject-tools-integration/Packages/manifest.json index d0841aedff..a3f60bc15a 100644 --- a/testproject-tools-integration/Packages/manifest.json +++ b/testproject-tools-integration/Packages/manifest.json @@ -1,8 +1,8 @@ { - "registry": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates", "dependencies": { "com.unity.ai.navigation": "2.0.5", - "com.unity.ide.rider": "3.0.31", + "com.unity.ide.rider": "3.0.34", + "com.unity.ide.visualstudio": "2.0.22", "com.unity.multiplayer.center": "1.0.0", "com.unity.multiplayer.tools": "2.2.3", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", diff --git a/testproject-tools-integration/Packages/packages-lock.json b/testproject-tools-integration/Packages/packages-lock.json index 126b02a19b..76d63d3876 100644 --- a/testproject-tools-integration/Packages/packages-lock.json +++ b/testproject-tools-integration/Packages/packages-lock.json @@ -7,52 +7,61 @@ "dependencies": { "com.unity.modules.ai": "1.0.0" }, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.8.19", - "depth": 2, + "version": "1.8.18", + "depth": 1, "source": "registry", "dependencies": { "com.unity.mathematics": "1.2.1", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.collections": { "version": "2.5.1", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": { "com.unity.burst": "1.8.17", - "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.test-framework": "1.4.5", + "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.test-framework.performance": "3.0.3" }, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { "version": "2.0.5", "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.31", + "version": "3.0.34", "depth": 0, "source": "registry", "dependencies": { "com.unity.ext.nunit": "1.0.6" }, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.22", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" }, "com.unity.mathematics": { "version": "1.3.2", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.multiplayer.center": { "version": "1.0.0", @@ -63,17 +72,19 @@ } }, "com.unity.multiplayer.tools": { - "version": "https://github.com/Unity-Technologies/com.unity.multiplayer.tools.git#f935904741c349dc41ba24fda6639041128e8f19", + "version": "2.2.3", "depth": 0, - "source": "git", + "source": "registry", "dependencies": { - "com.unity.profiling.core": "1.0.0-pre.1", - "com.unity.nuget.newtonsoft-json": "2.0.0", - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.collections": "1.1.0", - "com.unity.modules.uielements": "1.0.0" + "com.unity.burst": "1.8.18", + "com.unity.collections": "2.5.1", + "com.unity.mathematics": "1.3.2", + "com.unity.profiling.core": "1.0.2", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.modules.uielements": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.2.1" }, - "hash": "f935904741c349dc41ba24fda6639041128e8f19" + "url": "https://packages.unity.com" }, "com.unity.netcode.gameobjects": { "version": "file:../../com.unity.netcode.gameobjects", @@ -89,7 +100,21 @@ "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" + }, + "com.unity.nuget.newtonsoft-json": { + "version": "3.2.1", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.profiling.core": { + "version": "1.0.2", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" }, "com.unity.test-framework": { "version": "1.4.6", @@ -100,7 +125,7 @@ "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.test-framework.performance": { "version": "3.0.3", @@ -110,18 +135,18 @@ "com.unity.test-framework": "1.1.31", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.transport": { "version": "2.4.0", "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "2.2.1", "com.unity.burst": "1.8.12", + "com.unity.collections": "2.2.1", "com.unity.mathematics": "1.3.1" }, - "url": "https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-candidates" + "url": "https://packages.unity.com" }, "com.unity.modules.accessibility": { "version": "1.0.0", From e2ae8eb676dc907019cc6ba05beb438768bf5664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:48:21 +0100 Subject: [PATCH 187/236] ci: Added tracking information to ignored tests (#3308) This PR only modifies comments on ignored tests to track them and address those in the future via Jira Co-authored-by: Emma --- .../Tests/Editor/InterpolatorTests.cs | 4 ++-- .../Editor/Metrics/NetworkMetricsRegistrationTests.cs | 2 +- .../Tests/Runtime/DeferredMessagingTests.cs | 1 - .../Tests/Runtime/NetworkTransformAnticipationTests.cs | 3 +-- .../Tests/Runtime/NetworkVariable/NetworkVariableTests.cs | 8 ++++---- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index 4cdd06947e..b59f9093de 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -105,7 +105,7 @@ public void OutOfOrderShouldStillWork() Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f).Within(k_Precision)); } - [Ignore("TODO: Fix this test to still handle testing message loss without extrapolation")] + [Ignore("TODO: Fix this test to still handle testing message loss without extrapolation. This is tracked in MTT-11338")] [Test] public void MessageLoss() { @@ -306,7 +306,7 @@ public void TestUpdatingInterpolatorWithNoData() Assert.Throws(() => interpolator.Update(1f, serverTime)); } - [Ignore("TODO: Fix this test to still test duplicated values without extrapolation")] + [Ignore("TODO: Fix this test to still test duplicated values without extrapolation. This is tracked in MTT-11338")] [Test] public void TestDuplicatedValues() { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs index 82b88ec019..54114e2bd7 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Metrics/NetworkMetricsRegistrationTests.cs @@ -16,7 +16,7 @@ internal class NetworkMetricsRegistrationTests .ToArray(); [TestCaseSource(nameof(s_MetricTypes))] - [Ignore("Disable test while we reevaluate the assumption that INetworkMetricEvent interfaces must be reported from MLAPI.")] + [Ignore("Disable test while we reevaluate the assumption that INetworkMetricEvent interfaces must be reported from MLAPI. This ignored test is tracked in MTT-11339")] public void ValidateThatAllMetricTypesAreRegistered(Type metricType) { var dispatcher = new NetworkMetrics().Dispatcher as MetricDispatcher; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index bc97fd7b31..02cab89b5e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -495,7 +495,6 @@ public void WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDefer } [Test] - //[Ignore("Disabling this temporarily until it is migrated into new integration test.")] public void WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred() { m_SkipAddingPrefabsToClient = true; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 7e00fcbc8f..48d966b50a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -115,8 +115,7 @@ public AnticipatedNetworkTransform GetOtherClientComponent() } [Test] - // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on Android - [UnityPlatform(exclude = new[] { RuntimePlatform.Android })] + [UnityPlatform(exclude = new[] { RuntimePlatform.Android })] // TODO: this ignored test is tracked in MTT-11341 public void WhenAnticipating_ValueChangesImmediately() { var testComponent = GetTestComponent(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index cb062a6a64..28d1def735 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -2159,7 +2159,7 @@ public string ArrayStr(NativeArray arr) where T : unmanaged } [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on iOS + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeValueTypeNativeArrayNetworkVariables_ValuesAreSerializedCorrectly( @@ -2613,7 +2613,7 @@ public string DictionaryStr(Dictionary list) } [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on iOS + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesAreSerializedCorrectly( @@ -2800,7 +2800,7 @@ public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesA } [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is failing on iOS + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_ValuesAreSerializedCorrectly( @@ -2956,7 +2956,7 @@ public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_Valu } [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // I will add this test to track in Jira as next step (after having working CI in place). The job is unstable on mobile devices + [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] public void WhenSerializingAndDeserializingVeryLargeDictionaryNetworkVariables_ValuesAreSerializedCorrectly( From 1ac59f01d8b600f02878d52502caf5498d9be598 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 25 Feb 2025 09:50:37 -0600 Subject: [PATCH 188/236] fix: fastbufferreader does not honor arraysegment configuration (up port) (#3321) Up port of #3320 This PR resolves the issue where `FastBufferReader` did not provide constructors that would automatically use an `ArraySegment`'s configuration (Count and Offset) to define the `FastBufferReader`. [MTTB-34](https://jira.unity3d.com/browse/MTTB-34) fix: #2885 ## Changelog - Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset and the `ArraySegment.Count` as the `FastBufferReader` length. - Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator, int length = -1)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset. ## Testing and Documentation - Includes unit test. - Includes public API documentation. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++ .../Runtime/Serialization/FastBufferReader.cs | 48 +++++++++++++++++++ .../Serialization/FastBufferReaderTests.cs | 16 +++++++ 3 files changed, 67 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 6922d1eeda..13f6126579 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset and the `ArraySegment.Count` as the `FastBufferReader` length. (#3321) +- Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator, int length = -1)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset. (#3321) + ### Fixed - Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index bf30e04b4b..c856a6197b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -140,6 +140,54 @@ public unsafe FastBufferReader(ArraySegment buffer, Allocator copyAllocato } } + /// + /// Create a FastBufferReader from an ArraySegment that uses the ArraySegment.Offset for the reader's offset. + /// + /// A new buffer will be created using the given allocator and the value will be copied in. + /// FastBufferReader will then own the data. + /// + /// Allocator.None is not supported for byte[]. If you need this functionality, use a fixed() block + /// and ensure the FastBufferReader isn't used outside that block. + /// + /// The buffer to copy from + /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance + /// The number of bytes to copy (all if this is -1) + public unsafe FastBufferReader(ArraySegment buffer, Allocator copyAllocator, int length = -1) + { + if (copyAllocator == Allocator.None) + { + throw new NotSupportedException("Allocator.None cannot be used with managed source buffers."); + } + fixed (byte* data = buffer.Array) + { + + Handle = CreateHandle(data, length == -1 ? buffer.Count : length, buffer.Offset, copyAllocator, Allocator.Temp); + } + } + + /// + /// Create a FastBufferReader from an ArraySegment that uses the ArraySegment.Offset for the reader's offset and the ArraySegment.Count for the reader's length. + /// + /// A new buffer will be created using the given allocator and the value will be copied in. + /// FastBufferReader will then own the data. + /// + /// Allocator.None is not supported for byte[]. If you need this functionality, use a fixed() block + /// and ensure the FastBufferReader isn't used outside that block. + /// + /// The buffer to copy from + /// The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance + public unsafe FastBufferReader(ArraySegment buffer, Allocator copyAllocator) + { + if (copyAllocator == Allocator.None) + { + throw new NotSupportedException("Allocator.None cannot be used with managed source buffers."); + } + fixed (byte* data = buffer.Array) + { + Handle = CreateHandle(data, buffer.Count, buffer.Offset, copyAllocator, Allocator.Temp); + } + } + /// /// Create a FastBufferReader from an existing byte array. /// diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs index 30b1ba895b..020f7e6902 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -1579,5 +1579,21 @@ public unsafe void WhenCallingTryBeginReadInternal_AllowedReadPositionDoesNotMov Assert.AreEqual(reader.Handle->AllowedReadMark, 25); } } + + [Test] + public unsafe void WhenUsingArraySegment_ConstructorHonorsArraySegmentConfiguration() + { + var bytes = new byte[] { 0, 1, 2, 3 }; + var segment = new ArraySegment(bytes, 1, 3); + var reader = new FastBufferReader(segment, Allocator.Temp); + + var readerArray = reader.ToArray(); + Assert.True(readerArray.Length == bytes.Length - 1, $"Array of reader should have a length of {bytes.Length - 1} but was {readerArray.Length}!"); + for (int i = 0; i < readerArray.Length; i++) + { + Assert.True(bytes[i + 1] == readerArray[i], $"Value of {nameof(readerArray)} at index {i} is {readerArray[i]} but should be {bytes[i + 1]}!"); + } + reader.Dispose(); + } } } From 096c22c0f08e07214776d1f273c960e309e4e0b5 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 25 Feb 2025 14:34:42 -0600 Subject: [PATCH 189/236] fix: AnticipatedNetworkVariable not updating previous value [MTTB-1036] (#3306) This fixes the issue where the `AnticipatedNetworkVariable` previous value was never updated on the non-authority/write permission instances when the `OnAuthoritativeValueChanged` callback was invoked. [MTTB-1036](https://jira.unity3d.com/browse/MTTB-1036) Back port: #3322 ## Changelog - Fixed: Issue where the `AnticipatedNetworkVariable` previous value was never updated on the non-authority/write permission instances when the `OnAuthoritativeValueChanged` callback was invoked. ## Testing and Documentation - Includes integration test `NetworkVariableAnticipationTests.PreviousValueIsMaintainedProperly`. - No documentation changes or additions were necessary. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../AnticipatedNetworkVariable.cs | 3 ++ .../NetworkVariableAnticipationTests.cs | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 13f6126579..d3417214a5 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where `AnticipatedNetworkVariable` previous value returned by `AnticipatedNetworkVariable.OnAuthoritativeValueChanged` is updated correctly on the non-authoritative side. (#3306) - Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) - Fixed issue where the `NetworkObject.Ownership` custom editor did not take the default "Everything" flag into consideration. (#3305) - Fixed DestroyObject flow on non-authority game clients. (#3291) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index d9d4e87393..94625722e3 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -387,6 +387,9 @@ public override void ReadField(FastBufferReader reader) public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { m_AuthoritativeValue.ReadDelta(reader, keepDirtyDelta); + // Assure that the post delta read is invoked in order to update + // previous value. + m_AuthoritativeValue.PostDeltaRead(); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs index c436275fea..66d2d8d90f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs @@ -416,5 +416,39 @@ public void WhenStaleDataArrivesToReanticipatedVariable_ItIsAppliedAndReanticipa Assert.AreEqual(25, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value); Assert.AreEqual(20, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue); } + + + private int m_PreviousSnapValue; + /// + /// Validates the previous value is being properly updated on the non-authoritative side. + /// + [Test] + public void PreviousValueIsMaintainedProperly() + { + var testComponent = GetTestComponent(); + + testComponent.SnapOnAnticipationFailVariable.OnAuthoritativeValueChanged += OnAuthoritativeValueChanged; + testComponent.SnapOnAnticipationFailVariable.Anticipate(10); + testComponent.SetSnapValueRpc(10); + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + // Verify the previous value is 0 + Assert.AreEqual(0, m_PreviousSnapValue); + testComponent.SetSnapValueRpc(20); + + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + // Verify the previous value is 10 + Assert.AreEqual(10, m_PreviousSnapValue); + + testComponent.SetSnapValueRpc(30); + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + // Verify the previous value is 20 + Assert.AreEqual(20, m_PreviousSnapValue); + } + + private void OnAuthoritativeValueChanged(AnticipatedNetworkVariable anticipatedValue, in int previous, in int current) + { + m_PreviousSnapValue = previous; + } + } } From ca52b6c6b9b0511f55dcdba0123364981cf8fffe Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:46:06 +0100 Subject: [PATCH 190/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.41 (develop-2.0.0) (#3325) Updated wrench to 0.10.41 and regenerated recipes --- .yamato/wrench/api-validation-jobs.yml | 4 +-- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 38 +++++++++++++------------- .yamato/wrench/promotion-jobs.yml | 8 +++--- .yamato/wrench/recipe-regeneration.yml | 4 +-- .yamato/wrench/validation-jobs.yml | 36 ++++++++++++------------ .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 6b640688c9..3334510417 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -51,8 +51,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 9ea7ac892f..143680f296 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 46274fc09f..b3fbae202c 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,7 +16,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -67,10 +67,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -121,10 +121,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -176,10 +176,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -230,10 +230,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -284,10 +284,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -339,10 +339,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -393,10 +393,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -447,10 +447,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -502,8 +502,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 12b2d85339..4abb95c6c5 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -100,10 +100,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -204,8 +204,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index 299ecec419..d6e84df51b 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -11,7 +11,7 @@ test_-_wrench_jobs_up_to_date: commands: - command: dotnet run --project Tools\CI\NGO.Cookbook.csproj - command: |- - if [ -n "$(git status --porcelain)" ]; then + if [ -n "$(git status --porcelain -- .yamato/wrench)"]; then git status echo "Your repo is not clean - diff output:" git diff @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index ac84c7a8dd..6cca89d755 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.40.0 + UPMPVP_CONTEXT_WRENCH: 0.10.41.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.40.0 + Wrench: 0.10.41.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 90cd0ef491..80f256bd3f 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.40.0", + "wrench_version": "0.10.41.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index c6f2e29c41..2a506baab2 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From 063606af7f4302912ee5c3acadef0932bd607fd8 Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 09:42:57 +0100 Subject: [PATCH 191/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.42 (develop-2.0.0) (#3328) Updated Wrench to 0.10.42 and regenerated recipes --- .yamato/wrench/api-validation-jobs.yml | 4 +-- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 38 +++++++++++++------------- .yamato/wrench/promotion-jobs.yml | 8 +++--- .yamato/wrench/recipe-regeneration.yml | 4 +-- .yamato/wrench/validation-jobs.yml | 36 ++++++++++++------------ .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 3334510417..db4be07c35 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -51,8 +51,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 143680f296..6881d50692 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index b3fbae202c..5581ed9f89 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,7 +16,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -67,10 +67,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -121,10 +121,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -176,10 +176,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -230,10 +230,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -284,10 +284,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -339,10 +339,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -393,10 +393,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -447,10 +447,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -502,8 +502,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 4abb95c6c5..4ca7748e0d 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -100,10 +100,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -204,8 +204,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index d6e84df51b..ed1367053a 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -11,7 +11,7 @@ test_-_wrench_jobs_up_to_date: commands: - command: dotnet run --project Tools\CI\NGO.Cookbook.csproj - command: |- - if [ -n "$(git status --porcelain -- .yamato/wrench)"]; then + if [ -n "$(git status --porcelain -- .yamato/wrench)" ]; then git status echo "Your repo is not clean - diff output:" git diff @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 6cca89d755..50b2792480 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.41.0 + UPMPVP_CONTEXT_WRENCH: 0.10.42.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.41.0 + Wrench: 0.10.42.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 80f256bd3f..86bd9c13f5 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.41.0", + "wrench_version": "0.10.42.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index 2a506baab2..3c9167ff3d 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From dc13db44cabfca12cf4756f60bb63232d188c9f4 Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:09:47 +0100 Subject: [PATCH 192/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.43 (develop-2.0.0) (#3333) Updated Wrench to 0.10.43 and regenerated recipes --- .yamato/wrench/api-validation-jobs.yml | 6 +-- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 56 +++++++++++++------------- .yamato/wrench/promotion-jobs.yml | 12 +++--- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 54 ++++++++++++------------- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 8 files changed, 68 insertions(+), 68 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index db4be07c35..04b46bec54 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -13,7 +13,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -51,8 +51,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 6881d50692..a19766734f 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 5581ed9f89..14ee7f27fc 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,7 +16,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -26,7 +26,7 @@ preview_apv_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -67,10 +67,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -80,7 +80,7 @@ preview_apv_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -121,10 +121,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -135,7 +135,7 @@ preview_apv_-_6000_0_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -176,10 +176,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -189,7 +189,7 @@ preview_apv_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -230,10 +230,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -243,7 +243,7 @@ preview_apv_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -284,10 +284,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -298,7 +298,7 @@ preview_apv_-_6000_1_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -339,10 +339,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -352,7 +352,7 @@ preview_apv_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -393,10 +393,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -406,7 +406,7 @@ preview_apv_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -447,10 +447,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -461,7 +461,7 @@ preview_apv_-_6000_2_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -502,8 +502,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 4ca7748e0d..7404bb0e42 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -9,7 +9,7 @@ publish_dry_run_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -100,10 +100,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -113,7 +113,7 @@ publish_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -204,8 +204,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index ed1367053a..bfabbe4954 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 50b2792480..fcc7eff200 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -9,7 +9,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -75,7 +75,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -141,7 +141,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -207,7 +207,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -273,7 +273,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -339,7 +339,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -405,7 +405,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -471,7 +471,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects @@ -537,7 +537,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-58_ab06748e39f9b01f55b979c0c06d7c1f787e07cbca4dc6ae5eef274fd195612a.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.42.0 + UPMPVP_CONTEXT_WRENCH: 0.10.43.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.42.0 + Wrench: 0.10.43.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 86bd9c13f5..c66602bf7f 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.42.0", + "wrench_version": "0.10.43.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index 3c9167ff3d..1a1eba9f6b 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From 0e475bc613400bc4332eee4674f8953b4bd3be56 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 6 Mar 2025 15:09:12 -0500 Subject: [PATCH 193/236] fix: Never distribute an object to a client who is not an observer (#3323) [MTTB-1041](https://jira.unity3d.com/browse/MTTB-1041) ## Changelog - Fixed: ChangeOwnership should never distribute to a client that is not an observer of that object. ## Testing and Documentation - No tests have been added. Closes #3299 --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Connection/NetworkConnectionManager.cs | 126 ++++++++++-------- .../Runtime/Spawning/NetworkSpawnManager.cs | 40 +++--- .../OwnershipPermissionsTests.cs | 40 ++++++ 4 files changed, 139 insertions(+), 68 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d3417214a5..abc7731841 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed `ChangeOwnership` changing ownership to clients that are not observers. This also happened with automated object distribution. (#3323) - Fixed issue where `AnticipatedNetworkVariable` previous value returned by `AnticipatedNetworkVariable.OnAuthoritativeValueChanged` is updated correctly on the non-authoritative side. (#3306) - Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) - Fixed issue where the `NetworkObject.Ownership` custom editor did not take the default "Everything" flag into consideration. (#3305) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index fd418d34c2..f4f44301ae 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1122,18 +1122,16 @@ internal void OnClientDisconnectFromServer(ulong clientId) { if (!playerObject.DontDestroyWithOwner) { - // DANGO-TODO: This is something that would be best for CMB Service to handle as it is part of the disconnection process // 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.DistributedAuthorityMode && NetworkManager.DAHost) + 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 != null) + // 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; @@ -1158,10 +1156,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) } else if (!NetworkManager.ShutdownInProgress) { - if (!NetworkManager.ShutdownInProgress) - { - playerObject.RemoveOwnership(); - } + playerObject.RemoveOwnership(); } } @@ -1175,7 +1170,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) for (int i = clientOwnedObjects.Count - 1; i >= 0; i--) { var ownedObject = clientOwnedObjects[i]; - if (ownedObject != null) + if (ownedObject) { // If destroying with owner, then always despawn and destroy (or defer destroying to prefab handler) if (!ownedObject.DontDestroyWithOwner) @@ -1196,68 +1191,93 @@ internal void OnClientDisconnectFromServer(ulong clientId) } else if (!NetworkManager.ShutdownInProgress) { + // DANGO-TODO: We will want to match how the CMB service handles this. For now, we just try to evenly distribute + // ownership. // NOTE: All of the below code only handles ownership transfer. // For client-server, we just remove the ownership. // For distributed authority, we need to change ownership based on parenting if (NetworkManager.DistributedAuthorityMode) { - // Only NetworkObjects that have the OwnershipStatus.Distributable flag set and no parent - // (ownership is transferred to all children) will have their ownership redistributed. - if (ownedObject.IsOwnershipDistributable && ownedObject.GetCachedParent() == null && !ownedObject.IsOwnershipSessionOwner) + // Only NetworkObjects that have the OwnershipStatus.Distributable flag set and are not OwnershipSessionOwner are distributed. + // If the object has a parent - skip it for now, it will be distributed when its root parent is distributed. + if (!ownedObject.IsOwnershipDistributable || ownedObject.IsOwnershipSessionOwner || ownedObject.GetCachedParent()) + { + continue; + } + + if (ownedObject.IsOwnershipLocked) + { + ownedObject.SetOwnershipLock(false); + } + + var targetOwner = NetworkManager.ServerClientId; + // Cycle through the full count of clients to find + // the next viable owner. If none are found, then + // the DAHost defaults to the owner. + for (int j = 0; j < remainingClients.Count; j++) { - if (ownedObject.IsOwnershipLocked) + clientCounter++; + clientCounter = clientCounter % predictedClientCount; + if (ownedObject.Observers.Contains(remainingClients[clientCounter].ClientId)) { - ownedObject.SetOwnershipLock(false); + targetOwner = remainingClients[clientCounter].ClientId; + break; } + } + if (EnableDistributeLogging) + { + Debug.Log($"[Disconnected][Client-{clientId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); + } - // DANGO-TODO: We will want to match how the CMB service handles this. For now, we just try to evenly distribute - // ownership. - var targetOwner = NetworkManager.ServerClientId; - if (predictedClientCount > 1) + NetworkManager.SpawnManager.ChangeOwnership(ownedObject, targetOwner, true); + + // Ownership gets passed down to all children that have the same owner. + var childNetworkObjects = ownedObject.GetComponentsInChildren(); + foreach (var childObject in childNetworkObjects) + { + // We already changed ownership for this + if (childObject == ownedObject) { - clientCounter++; - clientCounter = clientCounter % predictedClientCount; - targetOwner = remainingClients[clientCounter].ClientId; + continue; } - if (EnableDistributeLogging) + // If the client owner disconnected, it is ok to unlock this at this point in time. + if (childObject.IsOwnershipLocked) { - Debug.Log($"[Disconnected][Client-{clientId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); + childObject.SetOwnershipLock(false); } - NetworkManager.SpawnManager.ChangeOwnership(ownedObject, targetOwner, true); - // DANGO-TODO: Should we try handling inactive NetworkObjects? - // Ownership gets passed down to all children that have the same owner. - var childNetworkObjects = ownedObject.GetComponentsInChildren(); - foreach (var childObject in childNetworkObjects) + + // Ignore session owner marked objects + if (childObject.IsOwnershipSessionOwner) { - // We already changed ownership for this - if (childObject == ownedObject) - { - continue; - } - // If the client owner disconnected, it is ok to unlock this at this point in time. - if (childObject.IsOwnershipLocked) - { - childObject.SetOwnershipLock(false); - } + continue; + } - // Ignore session owner marked objects - if (childObject.IsOwnershipSessionOwner) - { - continue; - } + // If the child's owner is not the client disconnected and the objects are marked with either distributable or transferable, then + // do not change ownership. + if (childObject.OwnerClientId != clientId && (childObject.IsOwnershipDistributable || childObject.IsOwnershipTransferable)) + { + continue; + } - // If the child's owner is not the client disconnected and the objects are marked with either distributable or transferable, then - // do not change ownership. - if (childObject.OwnerClientId != clientId && (childObject.IsOwnershipDistributable || childObject.IsOwnershipTransferable)) + var childOwner = targetOwner; + if (!childObject.Observers.Contains(childOwner)) + { + for (int j = 0; j < remainingClients.Count; j++) { - continue; + clientCounter++; + clientCounter = clientCounter % predictedClientCount; + if (ownedObject.Observers.Contains(remainingClients[clientCounter].ClientId)) + { + childOwner = remainingClients[clientCounter].ClientId; + break; + } } + } - NetworkManager.SpawnManager.ChangeOwnership(childObject, targetOwner, true); - if (EnableDistributeLogging) - { - Debug.Log($"[Disconnected][Client-{clientId}][Child of {ownedObject.NetworkObjectId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); - } + NetworkManager.SpawnManager.ChangeOwnership(childObject, childOwner, true); + if (EnableDistributeLogging) + { + Debug.Log($"[Disconnected][Client-{clientId}][Child of {ownedObject.NetworkObjectId}][NetworkObjectId-{ownedObject.NetworkObjectId} Distributed to Client-{targetOwner}"); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 9f219e4c35..d2319cb96d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -435,21 +435,16 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool // For client-server: // If ownership changes faster than the latency between the client-server and there are NetworkVariables being updated during ownership changes, // then notify the user they could potentially lose state updates if developer logging is enabled. - if (!NetworkManager.DistributedAuthorityMode && m_LastChangeInOwnership.ContainsKey(networkObject.NetworkObjectId) && m_LastChangeInOwnership[networkObject.NetworkObjectId] > Time.realtimeSinceStartup) + if (NetworkManager.LogLevel == LogLevel.Developer && !NetworkManager.DistributedAuthorityMode && m_LastChangeInOwnership.ContainsKey(networkObject.NetworkObjectId) && m_LastChangeInOwnership[networkObject.NetworkObjectId] > Time.realtimeSinceStartup) { - var hasNetworkVariables = false; for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++) { - hasNetworkVariables = networkObject.ChildNetworkBehaviours[i].NetworkVariableFields.Count > 0; - if (hasNetworkVariables) + if (networkObject.ChildNetworkBehaviours[i].NetworkVariableFields.Count > 0) { + NetworkLog.LogWarningServer($"[Rapid Ownership Change Detected][Potential Loss in State] Detected a rapid change in ownership that exceeds a frequency less than {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate! Provide at least {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate between ownership changes to avoid NetworkVariable state loss."); break; } } - if (hasNetworkVariables && NetworkManager.LogLevel == LogLevel.Developer) - { - NetworkLog.LogWarningServer($"[Rapid Ownership Change Detected][Potential Loss in State] Detected a rapid change in ownership that exceeds a frequency less than {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate! Provide at least {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate between ownership changes to avoid NetworkVariable state loss."); - } } if (NetworkManager.DistributedAuthorityMode) @@ -517,7 +512,6 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool throw new SpawnStateException("Object is not spawned"); } - if (networkObject.OwnerClientId == clientId && networkObject.PreviousOwnerId == clientId) { if (NetworkManager.LogLevel == LogLevel.Developer) @@ -527,6 +521,15 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool return; } + if (!networkObject.Observers.Contains(clientId)) + { + if (NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarningServer($"[Invalid Owner] Cannot send Ownership change as client-{clientId} cannot see {networkObject.name}! Use {nameof(NetworkObject.NetworkShow)} first."); + } + return; + } + // Used to distinguish whether a new owner should receive any currently dirty NetworkVariable updates networkObject.PreviousOwnerId = networkObject.OwnerClientId; @@ -1877,6 +1880,12 @@ internal void GetObjectDistribution(ref Dictionary + /// Distributes an even portion of spawned NetworkObjects to the given client. + /// This is called as part of the client connection process to ensure that all clients have a fair share of the spawned NetworkObjects. + /// DANGO-TODO: This will be handled by the CMB Service in the future. + /// + /// Client to distribute NetworkObjects to internal void DistributeNetworkObjects(ulong clientId) { if (!NetworkManager.DistributedAuthorityMode) @@ -1889,7 +1898,6 @@ internal void DistributeNetworkObjects(ulong clientId) return; } - // DA-NGO CMB SERVICE NOTES: // The most basic object distribution should be broken up into a table of spawned object types // where each type contains a list of each client's owned objects of that type that can be @@ -1956,6 +1964,11 @@ internal void DistributeNetworkObjects(ulong clientId) { if ((i % offsetCount) == 0) { + while (!ownerList.Value[i].Observers.Contains(clientId)) + { + i++; + } + var children = ownerList.Value[i].GetComponentsInChildren(); // Since the ownerList.Value[i] has to be distributable, then transfer all child NetworkObjects // with the same owner clientId and are marked as distributable also to the same client to keep @@ -1969,13 +1982,10 @@ internal void DistributeNetworkObjects(ulong clientId) } if (!child.IsOwnershipDistributable || !child.IsOwnershipTransferable) { - if (NetworkManager.LogLevel == LogLevel.Developer) - { - NetworkLog.LogWarning($"Sibling {child.name} of root parent {ownerList.Value[i].name} is neither transferrable or distributable! Object distribution skipped and could lead to a potentially un-owned or owner-mismatched {nameof(NetworkObject)}!"); - } + NetworkLog.LogWarning($"Sibling {child.name} of root parent {ownerList.Value[i].name} is neither transferable or distributable! Object distribution skipped and could lead to a potentially un-owned or owner-mismatched {nameof(NetworkObject)}!"); continue; } - // Transfer ownership of all distributable =or= transferrable children with the same owner to the same client to preserve the sibling ownership tree. + // Transfer ownership of all distributable =or= transferable children with the same owner to the same client to preserve the sibling ownership tree. ChangeOwnership(child, clientId, true); // Note: We don't increment the distributed count for these children as they are skipped when getting the object distribution } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs index dcf44f7d8f..4f70a864ad 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs @@ -400,6 +400,46 @@ public IEnumerator ValidateOwnershipPermissionsTest() AssertOnTimeout($"[Remove][Permissions Mismatch] {daHostInstance.name}: \n {m_ErrorLog}"); } + + [UnityTest] + public IEnumerator ChangeOwnershipWithoutObservers() + { + var initialLogLevel = m_ServerNetworkManager.LogLevel; + m_ServerNetworkManager.LogLevel = LogLevel.Developer; + var firstInstance = SpawnObject(m_PermissionsObject, m_ServerNetworkManager).GetComponent(); + OwnershipPermissionsTestHelper.CurrentOwnedInstance = firstInstance; + var firstInstanceHelper = firstInstance.GetComponent(); + var networkObjectId = firstInstance.NetworkObjectId; + m_ObjectToValidate = OwnershipPermissionsTestHelper.CurrentOwnedInstance; + yield return WaitForConditionOrTimeOut(ValidateObjectSpawnedOnAllClients); + AssertOnTimeout($"[Failed To Spawn] {firstInstance.name}: \n {m_ErrorLog}"); + + firstInstance.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable, true); + // Validate the base non-assigned permissions value for all instances are the same. + yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients); + AssertOnTimeout($"[Permissions Mismatch] {firstInstance.name}: \n {m_ErrorLog}"); + + var secondInstance = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[networkObjectId]; + + // Remove the client from the observers list + firstInstance.Observers.Remove(m_ClientNetworkManagers[0].LocalClientId); + + // ChangeOwnership should fail + firstInstance.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + LogAssert.Expect(LogType.Warning, "[Session-Owner Sender=0] [Invalid Owner] Cannot send Ownership change as client-1 cannot see PermObject{2}-OnServer{0}! Use NetworkShow first."); + Assert.True(firstInstance.IsOwner, $"[Ownership Check] Client-{m_ServerNetworkManager.LocalClientId} should still own this object!"); + + // Now re-add the client to the Observers list and try to change ownership + firstInstance.Observers.Add(m_ClientNetworkManagers[0].LocalClientId); + firstInstance.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + + // Validate the second client now owns the object + yield return WaitForConditionOrTimeOut(() => secondInstance.IsOwner); + AssertOnTimeout($"[Acquire Ownership Failed] Client-{m_ClientNetworkManagers[0].LocalClientId} failed to get ownership!"); + + m_ServerNetworkManager.LogLevel = initialLogLevel; + } + internal class OwnershipPermissionsTestHelper : NetworkBehaviour { public static NetworkObject CurrentOwnedInstance; From be34997b398f0049473904b2dc1de728b854a3cd Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 6 Mar 2025 22:22:20 -0500 Subject: [PATCH 194/236] fix: Block websocket use in dedicated server builds (#3336) Ensure users are not using websockets when building dedicated game server builds [MTT-11522](https://jira.unity3d.com/browse/MTT-11522) [MTTB-613](https://jira.unity3d.com/browse/MTTB-613) ## Changelog - Fixed: Blocks trying to create a dedicated server build with a websocket connection on Unity Transport. ## Testing and Documentation - No tests have been added. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index abc7731841..9a9a08bc6f 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -31,6 +31,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Ensured that a useful error is thrown when attempting to build a dedicated server with Unity Transport that uses websockets. (#3336) - Changed root in-scene placed `NetworkObject` instances now will always have either the `Distributable` permission set unless the `SessionOwner` permission is set. (#3305) - Changed the `DestroyObject` message to reduce the serialized message size and remove the unnecessary message field. (#3304) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index f8db2e30d9..f08e3cebaa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1668,6 +1668,21 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } #endif +#if UNITY_SERVER + if (m_ProtocolType == ProtocolType.RelayUnityTransport) + { + if (m_UseWebSockets) + { + Debug.LogError("Transport is configured to use Websockets, but websockets are not available on server builds. Ensure that the \"Use WebSockets\" checkbox is checked under \"Unity Transport\" component."); + } + + if (m_RelayServerData.IsWebSocket != 0) + { + Debug.LogError("Relay server data indicates usage of WebSockets, but websockets are not available on server builds. Be sure to use \"dtls\" or \"udp\" as the connection type when creating the server data"); + } + } +#endif + #if UTP_TRANSPORT_2_0_ABOVE if (m_UseEncryption) { From aa6f559e759a9ff7bf950b1146bd3362c2550f0d Mon Sep 17 00:00:00 2001 From: Ken Roecks Date: Sat, 8 Mar 2025 21:27:26 -0800 Subject: [PATCH 195/236] fixes --- .../Runtime/Core/NetworkLogScope.cs | 80 +++++++++++++++++++ .../SceneManagement/NetworkSceneManager.cs | 3 + .../Runtime/Spawning/NetworkSpawnManager.cs | 4 +- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs index a7fae61e3f..458c21c899 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkLogScope.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using UnityEngine; namespace TrollKing.Core { @@ -100,4 +102,82 @@ public void LogError(Exception e) UnityEngine.Debug.LogException(e); } } + + public static class NetworkGameObjectUtility + { + private static readonly NetworkLogScope Log = new NetworkLogScope(nameof(NetworkGameObjectUtility)); + + private static string NetworkGetScenePathRecursive(Transform go, string path) + { + if (go.parent == null) + { + return $"{go.gameObject.scene.name}:{go.name}:{path}"; + } + + return NetworkGetScenePathRecursive(go.parent, $"{go.name}:{path}"); + } + + // Depth first, we are going all the way down each leg + public static void NetworkGetAllHierarchyChildrenRecursive(this GameObject source, ref Queue queue) + { + if (source == null) + { + return; + } + + int children = source.transform.childCount; + for (int i = 0; i < children; i++) + { + var child = source.transform.GetChild(i); + var go = child.gameObject; + Log.Debug(() => $"AddingHierarchyChild {go}"); + queue.Enqueue(go); + NetworkGetAllHierarchyChildrenRecursive(go, ref queue); + } + } + + public static Queue NetworkGetAllHierarchyChildren(this GameObject root) + { + var retVal = new Queue(); + if (root == null) + { + return retVal; + } + Log.Debug(() => $"AddingHierarchyParent {root}"); + retVal.Enqueue(root); + root.NetworkGetAllHierarchyChildrenRecursive(ref retVal); + return retVal; + } + + public static string NetworkGetScenePath(this GameObject go) + { + return NetworkGetScenePath(go.transform); + } + + public static string NetworkGetScenePath(this Transform go) + { + return NetworkGetScenePathRecursive(go, ""); + } + + public static string NetworkGetSceneName(this GameObject go) + { + return go.scene.name; + } + + public static bool NetworkTryGetComponentInParent(this GameObject go, out T comp) + { + var parent = go.transform; + while (parent != null && parent.parent != parent) + { + if (parent.TryGetComponent(out comp)) + { + return true; + } + parent = parent.parent; + } + + comp = default; + return false; + } + } } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 171e8be7f5..775c1d5d58 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2855,6 +2855,7 @@ internal void MoveObjectsToDontDestroyOnLoad() /// internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true) { + Log.Info(() => "PopulateScenePlacedObjects"); if (clearScenePlacedObjects) { ScenePlacedObjects.Clear(); @@ -2874,6 +2875,7 @@ internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearSceneP { var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash; var sceneHandle = networkObjectInstance.gameObject.scene.handle; + Log.Info(() => $"PopulateScenePlacedObjects object={networkObjectInstance.gameObject} sceneHandle={sceneHandle} hash={globalObjectIdHash}"); // We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes) if (networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager || networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle) @@ -2889,6 +2891,7 @@ internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearSceneP } else { + Log.Error(() => $"Duplicate hash ids from object={ScenePlacedObjects[globalObjectIdHash][sceneHandle].gameObject.NetworkGetScenePath()}"); var exitingEntryName = ScenePlacedObjects[globalObjectIdHash][sceneHandle] != null ? ScenePlacedObjects[globalObjectIdHash][sceneHandle].name : "Null Entry"; throw new Exception($"{networkObjectInstance.name} tried to registered with {nameof(ScenePlacedObjects)} which already contains " + $"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {globalObjectIdHash} for {exitingEntryName}!"); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 854bc8fba2..64db5dbe3c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1450,11 +1450,11 @@ internal void ServerSpawnSceneObjectsOnStartSweep() } // Since we are spawing in-scene placed NetworkObjects for already loaded scenes, - // we need to add any in-scene placed NetworkObject to our tracking table + // we need to add any in-scene placed NetworkObject to our tracking table var clearFirst = true; foreach (var sceneLoaded in NetworkManager.SceneManager.ScenesLoaded) { - NetworkManager.SceneManager.PopulateScenePlacedObjects(sceneLoaded.Value, clearFirst); + NetworkManager.SceneManager.PopulateScenePlacedObjects(sceneLoaded.Value.SceneReference, clearFirst); clearFirst = false; } From fd19d67ce118f59334bf36a747279af3a5fa3bdf Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:08:25 +0100 Subject: [PATCH 196/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.44 (develop-2.0.0) (#3340) Update of wrench and regenerated recipes --- .yamato/wrench/api-validation-jobs.yml | 4 +-- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 38 +++++++++++++------------- .yamato/wrench/promotion-jobs.yml | 8 +++--- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 36 ++++++++++++------------ .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 8 files changed, 47 insertions(+), 47 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 04b46bec54..5167bb6727 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -51,8 +51,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index a19766734f..fd763f6fa5 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 14ee7f27fc..a6463fe189 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,7 +16,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -67,10 +67,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -121,10 +121,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -176,10 +176,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -230,10 +230,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -284,10 +284,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -339,10 +339,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -393,10 +393,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -447,10 +447,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -502,8 +502,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 7404bb0e42..4bef3a5a45 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -100,10 +100,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -204,8 +204,8 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index bfabbe4954..c2b89eecc2 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index fcc7eff200..7a5b3710e0 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.43.0 + UPMPVP_CONTEXT_WRENCH: 0.10.44.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.43.0 + Wrench: 0.10.44.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index c66602bf7f..3e50e220b0 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.43.0", + "wrench_version": "0.10.44.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index 1a1eba9f6b..d994233e00 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From e0da299fa30abaceac1bcb2ffe389bd64aa3cf2a Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 10 Mar 2025 15:06:56 -0400 Subject: [PATCH 197/236] chore: Update the port of the codec tests (#3341) - Add a configurable port for the DA codec tests (env variable: `ECHO_SERVER_PORT`) - Add an environment variable (`ENSURE_CODEC_TESTS`) that when set, will fail the codec tests if they fail to connect to an echo-server. The current/default behaviour is to ignore the tests when no echo-server is available - Run the rust echo-server inside yamato so that we can run the codec tests in our yamato tests - Ensure when running the codec tests that the `ENSURE_CODEC_TESTS` variable is set to ensure a test failure if the tests fail to connect to the echo-server [MTT-11549](https://jira.unity3d.com/browse/MTT-11549) ## Changelog - Added: Automated testing of the `DistributedAuthorityCodecTests` that run against a rust echo server built off the latest commit on their main branch --------- Co-authored-by: Noel Stephens --- .yamato/desktop-standalone-tests.yml | 42 ++++++++++++++----- .../DistributedAuthorityCodecTests.cs | 36 +++++++++++++++- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/.yamato/desktop-standalone-tests.yml b/.yamato/desktop-standalone-tests.yml index b12c71f2eb..bdacea3d49 100644 --- a/.yamato/desktop-standalone-tests.yml +++ b/.yamato/desktop-standalone-tests.yml @@ -7,7 +7,7 @@ # Builds are made on each supported editor as in project.metafile declaration # Builds are made with different scripting backends as in project.metafile declaration # ARM64 architectures are for now not considered since Windows_arm64 is recommended to use only after builds (would require separation here) and when it comes to macOS_arm64 there is problem with OpenCL not being available - + # Build phase {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} @@ -24,9 +24,10 @@ desktop_standalone_build_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ {% if platform.name == "ubuntu" %} - sudo apt-get update -q - sudo apt install -qy imagemagick + {% endif %} - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - + # Platform specific UTR setup - | {% if platform.name == "win" %} @@ -34,10 +35,10 @@ desktop_standalone_build_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ {% else %} curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr {% endif %} - + # Installing editor - unity-downloader-cli -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} --fast --wait - + # Build Player - | {% if platform.name == "win" %} @@ -53,17 +54,17 @@ desktop_standalone_build_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ logs: paths: - "artifacts/**/*" - + dependencies: - .yamato/project-pack.yml#project_pack_-_{{ project.name }}_{{ platform.name }} {% endfor -%} {% endfor -%} {% endfor -%} {% endfor -%} - - - - + + + + # Run phase {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} @@ -75,6 +76,16 @@ desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ type: {% if platform.name == "mac" %} {{ platform.type }} {% else %} {{ platform.type }}::GPU {% endif %} image: {{ platform.image }} flavor: {{ platform.flavor }} + +# Set additional variables for running the echo server +{% if platform.name != "win" %} + variables: + ECHO_SERVER_PORT: "7788" + # Set this to ensure the DA codec tests will fail if they cannot connect to the echo-server + # The default is to ignore the codec tests if the echo-server fails to connect + ENSURE_CODEC_TESTS: "true" +{% endif %} + commands: # Platform specific UTR setup - | @@ -86,7 +97,18 @@ desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - unity-downloader-cli -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} --fast --wait - + +# If ubuntu, run rust echo server +{% if platform.name != "win" %} + - git clone https://github.com/Unity-Technologies/mps-common-multiplayer-backend.git + # Install rust + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + # Build the echo server + - cd ./mps-common-multiplayer-backend/runtime && $HOME/.cargo/bin/cargo build --example ngo_echo_server + # Run the echo server in the background - this will reuse the artifacts from the build + - cd ./mps-common-multiplayer-backend/runtime && $HOME/.cargo/bin/cargo run --example ngo_echo_server -- --port $ECHO_SERVER_PORT & +{% endif %} + # Run Standalone tests - | {% if platform.name == "win" %} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 398d0403ee..2947b5830f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -16,6 +16,20 @@ namespace Unity.Netcode.RuntimeTests { + /// + /// This class tests the NGO message codec between the C# SDK and the Rust runtime. + /// + /// + /// These tests are run against a rust echo-server. + /// This server decodes incoming messages, and then re-encodes them before sending them back to the client. + /// Any errors in decoding or encoding messages will not echo the messages back, causing the tests to fail + /// No message handling logic is tested in these tests. They are only testing the codec. + /// The tests check if they can bind to a rust echo-server at the given address and port, if all tests are ignored. + /// The rust echo-server is run using `cargo run --example ngo_echo_server -- --port {port}` + /// The C# port can be configured using the environment variable "ECHO_SERVER_PORT" + /// The default behaviour when unity fails to connect to the echo-server is to ignore all tests in this class. + /// This can be overridden by setting the environment variable "ENSURE_CODEC_TESTS" to any value - then the tests will fail. + /// internal class DistributedAuthorityCodecTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; @@ -30,9 +44,19 @@ internal class DistributedAuthorityCodecTests : NetcodeIntegrationTest private NetworkManager Client => m_ClientNetworkManagers[0]; private string m_TransportHost = Environment.GetEnvironmentVariable("NGO_HOST") ?? "127.0.0.1"; - private const int k_TransportPort = 7777; + private static readonly ushort k_TransportPort = GetPortToBind(); private const int k_ClientId = 0; + /// + /// Configures the port to look for the rust echo-server. + /// + /// The port from the environment variable "ECHO_SERVER_PORT" if it is set and valid; otherwise uses port 7777 + private static ushort GetPortToBind() + { + var value = Environment.GetEnvironmentVariable("ECHO_SERVER_PORT"); + return ushort.TryParse(value, out var configuredPort) ? configuredPort : (ushort)7777; + } + private GameObject m_SpawnObject; internal class TestNetworkComponent : NetworkBehaviour @@ -54,7 +78,15 @@ protected override void OnOneTimeSetup() #else if (!CanConnectToServer(m_TransportHost, k_TransportPort)) { - Assert.Ignore("ignoring DA codec tests because UTP transport cannot connect to the runtime"); + var shouldFail = Environment.GetEnvironmentVariable("ENSURE_CODEC_TESTS"); + if (string.IsNullOrEmpty(shouldFail) || shouldFail.ToLower() == "false") + { + Assert.Ignore($"ignoring DA codec tests because UTP transport cannot connect to the rust echo-server at {m_TransportHost}:{k_TransportPort}"); + } + else + { + Assert.Fail($"Failed to connect to the rust echo-server at {m_TransportHost}:{k_TransportPort}"); + } } #endif base.OnOneTimeSetup(); From 429b256ae48a5cafa92153f9f261f718cfe0409e Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 10 Mar 2025 20:16:40 -0400 Subject: [PATCH 198/236] fix: Objects without observers are DestroyWithOwner (#3342) - Ensure objects with no observers (or only current owner as observer) are treated as if `DontDestroyWithOwner = true` on client disconnect - Explicitly check if childObject has new client as observer on client connect [MTTB-1041](https://jira.unity3d.com/browse/MTTB-1041) Changelog for this is in #3323. ## Changelog - Fixed: Ensure objects with no observers are treated as if `DontDestroyWithOwner = true` on client disconnect ## Testing and Documentation - No tests have been added. Co-authored-by: Noel Stephens --- .../Runtime/Connection/NetworkConnectionManager.cs | 13 ++++++++++--- .../Runtime/Spawning/NetworkSpawnManager.cs | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index f4f44301ae..a95df88a4e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1173,16 +1173,17 @@ internal void OnClientDisconnectFromServer(ulong clientId) if (ownedObject) { // If destroying with owner, then always despawn and destroy (or defer destroying to prefab handler) - if (!ownedObject.DontDestroyWithOwner) + // Handle an object with no observers other than the current disconnecting client as destroying with owner + if (!ownedObject.DontDestroyWithOwner || ownedObject.Observers.Count == 0 || (ownedObject.Observers.Count == 1 && ownedObject.Observers.Contains(clientId))) { - if (NetworkManager.PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash)) + if (NetworkManager.PrefabHandler.ContainsHandler(ownedObject.GlobalObjectIdHash)) { if (ownedObject.IsSpawned) { // Don't destroy (prefab handler will determine this, but always notify NetworkManager.SpawnManager.DespawnObject(ownedObject, false, true); } - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]); + NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(ownedObject); } else { @@ -1240,6 +1241,12 @@ internal void OnClientDisconnectFromServer(ulong clientId) { continue; } + + // Skip destroy with owner objects as they will be processed by the outer loop + if (!childObject.DontDestroyWithOwner || childObject.Observers.Count == 0 || (childObject.Observers.Count == 1 && childObject.Observers.Contains(clientId))) + { + continue; + } // If the client owner disconnected, it is ok to unlock this at this point in time. if (childObject.IsOwnershipLocked) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index d2319cb96d..7b390547a4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1975,8 +1975,8 @@ internal void DistributeNetworkObjects(ulong clientId) // the owned distributable parent with the owned distributable children foreach (var child in children) { - // Ignore the parent and any child that does not have the same owner or that is already owned by the currently targeted client - if (child == ownerList.Value[i] || child.OwnerClientId != ownerList.Value[i].OwnerClientId || child.OwnerClientId == clientId) + // Ignore any child that does not have the same owner, that is already owned by the currently targeted client, or that doesn't have the targeted client as an observer + if (child == ownerList.Value[i] || child.OwnerClientId != ownerList.Value[i].OwnerClientId || child.OwnerClientId == clientId || !child.Observers.Contains(clientId)) { continue; } From e5362a4c8fdc3e77e334b587202592e22297e3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Thu, 13 Mar 2025 16:59:39 +0100 Subject: [PATCH 199/236] ci: [NGOv2.X] Fixing WhenAnticipating_ValueChangesImmediately test (#3331) This PR focuses on addressing disabled WhenAnticipating_ValueChangesImmediately on Android devices which is located in `com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs` and tracked via MTT-11341 The error that is happening says `name: Unity.Netcode.RuntimeTests.NetworkTransformAnticipationTests.WhenAnticipating_ValueChangesImmediately result: FAILED message: Expected: (-0.28337, 0.21980, 0.06689, 0.93108) But was: (-0.28337, 0.21980, 0.06689, 0.93108) stackTrace: at Unity.Netcode.RuntimeTests.NetworkTransformAnticipationTests.WhenAnticipating_ValueChangesImmediately () [0x00000] in <00000000000000000000000000000000>:0 duration: 0.023017 seconds` Which seems to be an FP comparison problem (since the values seem to be correct). The approach is to use quaternion comparer with default error value of 0.000001 to mitigate this issue ([DOCS](https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/reference-comparer-quaternion.html)) --- .../Tests/Runtime/NetworkTransformAnticipationTests.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 48d966b50a..b9a361ef03 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -5,7 +5,7 @@ using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; -using UnityEngine.TestTools; +using UnityEngine.TestTools.Utils; using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests @@ -115,10 +115,10 @@ public AnticipatedNetworkTransform GetOtherClientComponent() } [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.Android })] // TODO: this ignored test is tracked in MTT-11341 public void WhenAnticipating_ValueChangesImmediately() { var testComponent = GetTestComponent(); + var quaternionComparer = new QuaternionEqualityComparer(0.000001f); testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); @@ -126,12 +126,11 @@ public void WhenAnticipating_ValueChangesImmediately() Assert.AreEqual(new Vector3(0, 1, 2), testComponent.transform.position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.localScale); - Assert.AreEqual(Quaternion.LookRotation(new Vector3(2, 3, 4)), testComponent.transform.rotation); + Assert.That(testComponent.transform.rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AnticipatedState.Position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Scale); - Assert.AreEqual(Quaternion.LookRotation(new Vector3(2, 3, 4)), testComponent.AnticipatedState.Rotation); - + Assert.That(testComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. } [Test] From 16c440900a1df64a48751f76f51666e4c0d7e5b8 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Mar 2025 23:17:26 -0500 Subject: [PATCH 200/236] fix: networktransform double lerp (MTTB-539) (#3337) This PR fixes the issue with double lerping by providing two types of interpolators to select from while also exposing the `MaximumInterpolationTime` for each respective interpolator type. Smooth Dampening is added as an alternative to using lerp for each respective interpolator (position, rotation, and scale). This PR includes a few new additions: - Additional updates to the inspector view which allows for interpolator type selection and if available additional properties associated with the interpolator. ![image](https://github.com/user-attachments/assets/f643c015-646f-4c1a-a979-05a6287b41d7) ![image](https://github.com/user-attachments/assets/335cd68d-98fd-44b7-b6f3-88c4d44a60fd) ![image](https://github.com/user-attachments/assets/63c6d44c-09a4-4bad-b74e-46218a3a4b8c) ![image](https://github.com/user-attachments/assets/d966dd7c-0c82-4bcd-85ce-e7cf8455d3a6) - A new `NetworkTimeSystem.TickLatency` property that provides the average latency of a client. - An new `NetworkTime.FixedDeltaTimeAsDouble` property that returns a `double` version of the `NetworkTime.FixedDeltaTime`. This PR also starts the deprecation process for `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as this method is only for internal testing purposes. [MTTB-539](https://jira.unity3d.com/browse/MTTB-539) [MTT-11338](https://jira.unity3d.com/browse/MTT-11338) -- Fixing disabled tests fix: #3112 close: #3112 ## Changelog - Added: Interpolator types as an inspector view selection for position, rotation, and scale. - Added: A new smooth dampening interpolator type that provides a nice balance between precision and smoothing results. - Added: `NetworkTimeSystem.TickLatency` property that provides the average latency of a client. - Fixed: Issue where the `MaximumInterpolationTime` could not be modified from within the inspector view or runtime. - Changed: `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as being deprecated since this method is only used for internal testing purposes. ## Testing and Documentation - Includes integration test updates: - `InterpolatorTests` was updated to changes and two disabled tests were refactored and re-enabled. - `NetworkTransformGeneral` includes additional TestFixture passes using Smooth Dampening. - `NetworktTransformTests` includes additional TestFixture passes using Smooth Dampening. - `NestedNetworkTransformTests` includes additional TestFixture passes using Smooth Dampening. - Includes updates and additions to public documentation ([PR-1443](https://github.com/Unity-Technologies/com.unity.multiplayer.docs/pull/1443)) --- com.unity.netcode.gameobjects/CHANGELOG.md | 5 + .../NetcodeForGameObjectsProjectSettings.cs | 13 + .../Configuration/NetworkPrefabProcessor.cs | 3 + .../Configuration/NetworkPrefabsEditor.cs | 4 + .../Editor/NetcodeEditorBase.cs | 73 +- .../Editor/NetworkTransformEditor.cs | 85 +- .../Runtime/Components/HalfVector3.cs | 4 +- .../Runtime/Components/HalfVector4.cs | 4 +- .../BufferedLinearInterpolator.cs | 735 +++++++++++------- .../BufferedLinearInterpolatorFloat.cs | 42 + .../BufferedLinearInterpolatorFloat.cs.meta | 2 + .../BufferedLinearInterpolatorQuaternion.cs | 82 ++ ...fferedLinearInterpolatorQuaternion.cs.meta | 2 + .../BufferedLinearInterpolatorVector3.cs | 77 ++ .../BufferedLinearInterpolatorVector3.cs.meta | 2 + .../Components/NetworkDeltaPosition.cs | 6 +- .../Runtime/Components/NetworkTransform.cs | 363 ++++++--- .../RigidbodyContactEventManager.cs | 4 + .../Runtime/Timing/NetworkTime.cs | 14 +- .../Runtime/Timing/NetworkTimeSystem.cs | 31 +- .../Tests/Editor/InterpolatorTests.cs | 126 +-- .../NetworkTransformGeneral.cs | 42 +- .../NetworkTransform/NetworkTransformTests.cs | 77 +- pvpExceptions.json | 42 - .../AutomatedPlayerMover.cs | 4 - .../NestedNetworkTransformTests.cs | 25 +- 26 files changed, 1279 insertions(+), 588 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9a9a08bc6f..1bffdbce19 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,11 +10,15 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added interpolator types as an inspector view selection for position, rotation, and scale. (#3337) +- Added a new smooth dampening interpolator type that provides a nice balance between precision and smoothing results. (#3337) +- Added `NetworkTimeSystem.TickLatency` property that provides the average latency of a client. (#3337) - Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset and the `ArraySegment.Count` as the `FastBufferReader` length. (#3321) - Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator, int length = -1)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset. (#3321) ### Fixed +- Fixed issue where the `MaximumInterpolationTime` could not be modified from within the inspector view or runtime. (#3337) - Fixed `ChangeOwnership` changing ownership to clients that are not observers. This also happened with automated object distribution. (#3323) - Fixed issue where `AnticipatedNetworkVariable` previous value returned by `AnticipatedNetworkVariable.OnAuthoritativeValueChanged` is updated correctly on the non-authoritative side. (#3306) - Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) @@ -31,6 +35,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as being deprecated since this method is only used for internal testing purposes. (#3337) - Ensured that a useful error is thrown when attempting to build a dedicated server with Unity Transport that uses websockets. (#3336) - Changed root in-scene placed `NetworkObject` instances now will always have either the `Distributable` permission set unless the `SessionOwner` permission is set. (#3305) - Changed the `DestroyObject` message to reduce the serialized message size and remove the unnecessary message field. (#3304) diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs index e5d18b6bb8..52bd0ef011 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs @@ -3,11 +3,21 @@ namespace Unity.Netcode.Editor.Configuration { + /// + /// A of type . + /// [FilePath("ProjectSettings/NetcodeForGameObjects.asset", FilePathAttribute.Location.ProjectFolder)] public class NetcodeForGameObjectsProjectSettings : ScriptableSingleton { internal static readonly string DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset"; + /// + /// The path and name for the DefaultNetworkPrefabs asset. + /// [SerializeField] public string NetworkPrefabsPath = DefaultNetworkPrefabsPath; + + /// + /// A temporary network prefabs path used internally. + /// public string TempNetworkPrefabsPath; private void OnEnable() @@ -19,6 +29,9 @@ private void OnEnable() TempNetworkPrefabsPath = NetworkPrefabsPath; } + /// + /// Used to determine whether the default network prefabs asset should be generated or not. + /// [SerializeField] public bool GenerateDefaultNetworkPrefabs = true; diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs index 55f5fcbfc4..f74530e60f 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs @@ -9,6 +9,9 @@ namespace Unity.Netcode.Editor.Configuration /// public class NetworkPrefabProcessor : AssetPostprocessor { + /// + /// The path to the default network prefabs list. + /// public static string DefaultNetworkPrefabsPath { get diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs index 0845068be7..68113ca32f 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs @@ -4,6 +4,9 @@ namespace Unity.Netcode.Editor { + /// + /// The custom editor for the . + /// [CustomEditor(typeof(NetworkPrefabsList), true)] [CanEditMultipleObjects] public class NetworkPrefabsEditor : UnityEditor.Editor @@ -82,6 +85,7 @@ private void OnEnable() m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs"); } + /// public override void OnInspectorGUI() { using (new EditorGUI.DisabledScope(true)) diff --git a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs index 4b8a351a35..b61a77e6b6 100644 --- a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs @@ -8,18 +8,87 @@ namespace Unity.Netcode.Editor /// The base Netcode Editor helper class to display derived based components
/// where each child generation's properties will be displayed within a FoldoutHeaderGroup. ///
+ /// + /// Defines the base derived component type where 's type T + /// refers to any child derived class of . This provides the ability to have multiple child generation derived custom + /// editos that each child derivation handles drawing its unique properies from that of its parent class. + /// + /// The base derived component type [CanEditMultipleObjects] public partial class NetcodeEditorBase : UnityEditor.Editor where TT : MonoBehaviour { + private const int k_IndentOffset = 15; + private int m_IndentOffset = 0; + private int m_IndentLevel = 0; + private float m_OriginalLabelWidth; + /// public virtual void OnEnable() { } + /// + /// Draws a with the option to provide the font style to use. + /// + /// The serialized property () to draw within the inspector view. + /// The font style () to use when drawing the label of the property field. + private protected void DrawPropertyField(SerializedProperty property, FontStyle fontStyle = FontStyle.Normal) + { + var originalWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = originalWidth - (m_IndentOffset * m_IndentLevel); + var originalLabelFontStyle = EditorStyles.label.fontStyle; + EditorStyles.label.fontStyle = fontStyle; + EditorGUILayout.PropertyField(property); + EditorStyles.label.fontStyle = originalLabelFontStyle; + EditorGUIUtility.labelWidth = originalWidth; + } + + /// + /// Will begin a new indention level for drawing propery fields. + /// + /// + /// You *must* call when returning back to the previous indention level.
+ /// For additional information:
+ /// -
+ /// -
+ ///
+ /// (optional) The size in pixels of the offset. If no value passed, then it uses a default of 15 pixels. + private protected void BeginIndent(int offset = k_IndentOffset) + { + m_IndentOffset = offset; + m_IndentLevel++; + GUILayout.BeginHorizontal(); + GUILayout.Space(m_IndentOffset); + GUILayout.BeginVertical(); + } + + /// + /// Will end the current indention level when drawing propery fields. + /// + /// + /// For additional information:
+ /// -
+ /// -
+ ///
+ private protected void EndIndent() + { + if (m_IndentLevel == 0) + { + Debug.LogWarning($"Invoking {nameof(EndIndent)} when the indent level is already 0!"); + return; + } + m_IndentLevel--; + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + /// /// Helper method to draw the properties of the specified child type component within a FoldoutHeaderGroup. /// - /// The specific child type that should have its properties drawn. + /// + /// must be a sub-class of the root parent class type . + /// + /// The specific child derived type of or the type of that should have its properties drawn. /// The component type of the . /// The to invoke that will draw the type properties. /// The current expanded property value @@ -30,9 +99,11 @@ protected void DrawFoldOutGroup(Type type, Action displayProperties, bool exp EditorGUI.BeginChangeCheck(); serializedObject.Update(); var currentClass = typeof(T); + if (type.IsSubclassOf(currentClass) || (!type.IsSubclassOf(currentClass) && currentClass.IsSubclassOf(typeof(TT)))) { var expandedValue = EditorGUILayout.BeginFoldoutHeaderGroup(expanded, $"{currentClass.Name} Properties"); + if (expandedValue) { EditorGUILayout.EndFoldoutHeaderGroup(); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index e93226e2bd..b04ad3f724 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -28,6 +28,13 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_ScaleThresholdProperty; private SerializedProperty m_InLocalSpaceProperty; private SerializedProperty m_InterpolateProperty; + private SerializedProperty m_PositionInterpolationTypeProperty; + private SerializedProperty m_RotationInterpolationTypeProperty; + private SerializedProperty m_ScaleInterpolationTypeProperty; + + private SerializedProperty m_PositionMaximumInterpolationTimeProperty; + private SerializedProperty m_RotationMaximumInterpolationTimeProperty; + private SerializedProperty m_ScaleMaximumInterpolationTimeProperty; private SerializedProperty m_UseQuaternionSynchronization; private SerializedProperty m_UseQuaternionCompression; @@ -36,6 +43,7 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_AuthorityMode; private static int s_ToggleOffset = 45; + private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5; private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position"); private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation"); @@ -61,6 +69,15 @@ public override void OnEnable() m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold)); m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace)); m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate)); + + m_PositionInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.PositionInterpolationType)); + m_PositionMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.PositionMaxInterpolationTime)); + m_RotationInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.RotationInterpolationType)); + m_RotationMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.RotationMaxInterpolationTime)); + m_ScaleInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleInterpolationType)); + m_ScaleMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleMaxInterpolationTime)); + + m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization)); m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression)); m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision)); @@ -141,9 +158,21 @@ private void DisplayNetworkTransformProperties() } EditorGUILayout.Space(); EditorGUILayout.LabelField("Thresholds", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_PositionThresholdProperty); - EditorGUILayout.PropertyField(m_RotAngleThresholdProperty); - EditorGUILayout.PropertyField(m_ScaleThresholdProperty); + if (networkTransform.SynchronizePosition) + { + EditorGUILayout.PropertyField(m_PositionThresholdProperty); + } + + if (networkTransform.SynchronizeRotation) + { + EditorGUILayout.PropertyField(m_RotAngleThresholdProperty); + } + + if (networkTransform.SynchronizeScale) + { + EditorGUILayout.PropertyField(m_ScaleThresholdProperty); + } + EditorGUILayout.Space(); EditorGUILayout.LabelField("Delivery", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_TickSyncChildren); @@ -158,9 +187,53 @@ private void DisplayNetworkTransformProperties() EditorGUILayout.PropertyField(m_InLocalSpaceProperty); if (!networkTransform.HideInterpolateValue) { - EditorGUILayout.PropertyField(m_InterpolateProperty); + if (networkTransform.Interpolate) + { + EditorGUILayout.Space(); + } + DrawPropertyField(m_InterpolateProperty, networkTransform.Interpolate ? FontStyle.Bold : FontStyle.Normal); + if (networkTransform.Interpolate) + { + BeginIndent(); + if (networkTransform.SynchronizePosition) + { + DrawPropertyField(m_PositionInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + BeginIndent(); + DrawPropertyField(m_SlerpPosition); + DrawPropertyField(m_PositionMaximumInterpolationTimeProperty); + EndIndent(); + } + } + if (networkTransform.SynchronizeRotation) + { + DrawPropertyField(m_RotationInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + BeginIndent(); + DrawPropertyField(m_RotationMaximumInterpolationTimeProperty); + EndIndent(); + } + } + if (networkTransform.SynchronizeScale) + { + DrawPropertyField(m_ScaleInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + BeginIndent(); + DrawPropertyField(m_ScaleMaximumInterpolationTimeProperty); + EndIndent(); + } + } + EndIndent(); + EditorGUILayout.Space(); + } } - EditorGUILayout.PropertyField(m_SlerpPosition); + EditorGUILayout.PropertyField(m_UseQuaternionSynchronization); if (m_UseQuaternionSynchronization.boolValue) { @@ -190,8 +263,6 @@ private void DisplayNetworkTransformProperties() #endif // COM_UNITY_MODULES_PHYSICS2D } - - /// public override void OnInspectorGUI() { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs index ce696ec965..d9367b010a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs @@ -74,9 +74,7 @@ private void SerializeRead(FastBufferReader reader) } } - /// - /// The serialization implementation of . - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { if (serializer.IsReader) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs index 1ff2b09076..8d7a000f38 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs @@ -58,9 +58,7 @@ private void SerializeRead(FastBufferReader reader) } } - /// - /// The serialization implementation of . - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { if (serializer.IsReader) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index d5fbaaa5cf..0aabd0226e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -11,37 +11,13 @@ namespace Unity.Netcode /// The type of interpolated value public abstract class BufferedLinearInterpolator where T : struct { - internal float MaxInterpolationBound = 3.0f; - protected internal struct BufferedItem - { - public T Item; - public double TimeSent; - - public BufferedItem(T item, double timeSent) - { - Item = item; - TimeSent = timeSent; - } - } - - /// - /// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one. - /// - public float MaximumInterpolationTime = 0.1f; - + // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so + // that we don't have a very small buffer because of this. + private const int k_BufferCountLimit = 100; + private const float k_AproximatePrecision = 0.0001f; private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator - protected internal T m_InterpStartValue; - protected internal T m_CurrentInterpValue; - protected internal T m_InterpEndValue; - - private double m_EndTimeConsumed; - private double m_StartTimeConsumed; - - protected internal readonly List m_Buffer = new List(k_BufferCountLimit); - - - + #region Legacy notes // Buffer consumption scenarios // Perfect case consumption // | 1 | 2 | 3 | @@ -64,130 +40,410 @@ public BufferedItem(T item, double timeSent) // | 4 | 5 | 6 | 7 | 8 | --> consume all and teleport to last value <8> --> this is the nuclear option, ideally this example would consume 4 and 5 // instead of jumping to 8, but since in OnValueChange we don't yet have an updated server time (updated in pre-update) to know which value // we should keep and which we should drop, we don't have enough information to do this. Another thing would be to not have the burst in the first place. + #endregion - // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so - // that we don't have a very small buffer because of this. - private const int k_BufferCountLimit = 100; - private BufferedItem m_LastBufferedItemReceived; - private int m_NbItemsReceivedThisFrame; - - private int m_LifetimeConsumedCount; + #region Properties being deprecated + /// + /// The legacy list of items. + /// + /// + /// This is replaced by the of type . + /// + [Obsolete("This list is no longer used and will be deprecated.", false)] + protected internal readonly List m_Buffer = new List(); - private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0; + /// + /// ** Deprecating ** + /// The starting value of type to interpolate from. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpStartValue; - internal bool EndOfBuffer => m_Buffer.Count == 0; + /// + /// ** Deprecating ** + /// The current value of type . + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_CurrentInterpValue; - internal bool InLocalSpace; + /// + /// ** Deprecating ** + /// The end (or target) value of type to interpolate towards. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpEndValue; + #endregion - protected internal virtual void OnConvertTransformSpace(Transform transform, bool inLocalSpace) + /// + /// Represents a buffered item measurement. + /// + protected internal struct BufferedItem { + /// + /// THe item identifier + /// + public int ItemId; + /// + /// The item value + /// + public T Item; + /// + /// The time the item was sent. + /// + public double TimeSent; + /// + /// The constructor. + /// + /// The item value. + /// The time the item was sent. + /// The item identifier + public BufferedItem(T item, double timeSent, int itemId) + { + Item = item; + TimeSent = timeSent; + ItemId = itemId; + } } - internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) + /// + /// The current internal state of the . + /// + /// + /// Not public API ready yet. + /// + internal struct CurrentState { - OnConvertTransformSpace(transform, inLocalSpace); - InLocalSpace = inLocalSpace; + public BufferedItem? Target; + + public double StartTime; + public double EndTime; + public float TimeToTargetValue; + public float DeltaTime; + public float LerpT; + + public T CurrentValue; + public T PreviousValue; + + private float m_AverageDeltaTime; + + public float AverageDeltaTime => m_AverageDeltaTime; + public float FinalTimeToTarget => TimeToTargetValue - DeltaTime; + + public void AddDeltaTime(float deltaTime) + { + if (m_AverageDeltaTime == 0.0f) + { + m_AverageDeltaTime = deltaTime; + } + else + { + m_AverageDeltaTime += deltaTime; + m_AverageDeltaTime *= 0.5f; + } + DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); + LerpT = TimeToTargetValue == 0.0f ? 1.0f : DeltaTime / TimeToTargetValue; + } + + public void ResetDelta() + { + m_AverageDeltaTime = 0.0f; + DeltaTime = 0.0f; + } + + public bool TargetTimeAproximatelyReached() + { + if (!Target.HasValue) + { + return false; + } + return m_AverageDeltaTime >= FinalTimeToTarget; + } + + public void Reset(T currentValue) + { + Target = null; + CurrentValue = currentValue; + PreviousValue = currentValue; + // When reset, we consider ourselves to have already arrived at the target (even if no target is set) + LerpT = 0.0f; + EndTime = 0.0; + StartTime = 0.0; + ResetDelta(); + } } /// - /// Resets interpolator to initial state + /// Determines how much smoothing will be applied to the 2nd lerp when using the (i.e. lerping and not smooth dampening). + /// + /// + /// There's two factors affecting interpolation:
+ /// - Buffering: Which can be adjusted in set in the .
+ /// - Interpolation time: The divisor applied to delta time where the quotient is used as the lerp time. + ///
+ [Range(0.016f, 1.0f)] + public float MaximumInterpolationTime = 0.1f; + + /// + /// The current buffered items received by the authority. + /// + protected internal readonly Queue m_BufferQueue = new Queue(k_BufferCountLimit); + + /// + /// The current interpolation state + /// + internal CurrentState InterpolateState; + + /// + /// The maximum Lerp "t" boundary when using standard lerping for interpolation + /// + internal float MaxInterpolationBound = 3.0f; + internal bool EndOfBuffer => m_BufferQueue.Count == 0; + internal bool InLocalSpace; + + private double m_LastMeasurementAddedTime = 0.0f; + private int m_BufferCount; + private int m_NbItemsReceivedThisFrame; + private BufferedItem m_LastBufferedItemReceived; + /// + /// Represents the rate of change for the value being interpolated when smooth dampening is enabled. + /// + private T m_RateOfChange; + + /// + /// Represents the predicted rate of change for the value being interpolated when smooth dampening is enabled. + /// + private T m_PredictedRateOfChange; + + /// + /// When true, the value is an angular numeric representation. + /// + private protected bool m_IsAngularValue; + + /// + /// Resets interpolator to the defaults. /// public void Clear() { - m_Buffer.Clear(); - m_EndTimeConsumed = 0.0d; - m_StartTimeConsumed = 0.0d; + m_BufferQueue.Clear(); + m_BufferCount = 0; + m_LastMeasurementAddedTime = 0.0; + InterpolateState.Reset(default); + m_RateOfChange = default; + m_PredictedRateOfChange = default; } /// - /// Teleports current interpolation value to targetValue. + /// Resets the current interpolator to the target value. /// - /// The target value to teleport instantly + /// + /// This is used when first synchronizing/initializing and when telporting an object. + /// + /// The target value to reset the interpolator to /// The current server time - public void ResetTo(T targetValue, double serverTime) + /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. + public void ResetTo(T targetValue, double serverTime, bool isAngularValue = false) { - m_LifetimeConsumedCount = 1; - m_InterpStartValue = targetValue; - m_InterpEndValue = targetValue; - m_CurrentInterpValue = targetValue; - m_Buffer.Clear(); - m_EndTimeConsumed = 0.0d; - m_StartTimeConsumed = 0.0d; - - Update(0, serverTime, serverTime); + // Clear the interpolator + Clear(); + InternalReset(targetValue, serverTime, isAngularValue); } - // todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path - private void TryConsumeFromBuffer(double renderTime, double serverTime) + private void InternalReset(T targetValue, double serverTime, bool isAngularValue = false, bool addMeasurement = true) { - int consumedCount = 0; - // only consume if we're ready - - // this operation was measured as one of our most expensive, and we should put some thought into this. - // NetworkTransform has (currently) 7 buffered linear interpolators (3 position, 3 scale, 1 rot), and - // each has its own independent buffer and 'm_endTimeConsume'. That means every frame I have to do 7x - // these checks vs. if we tracked these values in a unified way - if (renderTime >= m_EndTimeConsumed) + m_RateOfChange = default; + m_PredictedRateOfChange = default; + // Set our initial value + InterpolateState.Reset(targetValue); + m_IsAngularValue = isAngularValue; + + if (addMeasurement) { - BufferedItem? itemToInterpolateTo = null; - // assumes we're using sequenced messages for netvar syncing - // buffer contains oldest values first, iterating from end to start to remove elements from list while iterating + // Add the first measurement for our baseline + AddMeasurement(targetValue, serverTime); + } + } - // calling m_Buffer.Count shows up hot in the profiler. - for (int i = m_Buffer.Count - 1; i >= 0; i--) // todo stretch: consume ahead if we see we're missing values due to packet loss + #region Smooth Dampening Interpolation + /// + /// TryConsumeFromBuffer: Smooth Dampening Version + /// + /// render time: the time in "ticks ago" relative to the current tick latency + /// minimum time delta (defaults to tick frequency) + /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer + private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) + { + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime + && (InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) + { + BufferedItem? previousItem = null; + var startTime = 0.0; + var alreadyHasBufferItem = false; + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { - var bufferedValue = m_Buffer[i]; - // Consume when ready and interpolate to last value we can consume. This can consume multiple values from the buffer - if (bufferedValue.TimeSent <= serverTime) + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } + + // If we haven't set a target or the potential item's time sent is less that the current target's time sent + // then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there + // was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under + // this scenario, we just want to continue pulling items from the queue until the last item pulled from the + // queue is greater than the redner time or greater than the currently targeted item. + if (!InterpolateState.Target.HasValue || + ((potentialItem.TimeSent <= renderTime) && InterpolateState.Target.Value.TimeSent <= potentialItem.TimeSent)) { - if (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent > itemToInterpolateTo.Value.TimeSent) + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { - if (m_LifetimeConsumedCount == 0) - { - // if interpolator not initialized, teleport to first value when available - m_StartTimeConsumed = bufferedValue.TimeSent; - m_InterpStartValue = bufferedValue.Item; - } - else if (consumedCount == 0) + if (!InterpolateState.Target.HasValue) { - // Interpolating to new value, end becomes start. We then look in our buffer for a new end. - m_StartTimeConsumed = m_EndTimeConsumed; - m_InterpStartValue = m_InterpEndValue; - } + InterpolateState.Target = target; - if (bufferedValue.TimeSent > m_EndTimeConsumed) + alreadyHasBufferItem = true; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.TimeToTargetValue = minDeltaTime; + startTime = InterpolateState.Target.Value.TimeSent; + } + else { - itemToInterpolateTo = bufferedValue; - m_EndTimeConsumed = bufferedValue.TimeSent; - m_InterpEndValue = bufferedValue.Item; + if (!alreadyHasBufferItem) + { + alreadyHasBufferItem = true; + startTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.LerpT = 0.0f; + } + // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated + // for each item as opposed to losing the resolution of the values. + InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + + InterpolateState.Target = target; } + InterpolateState.ResetDelta(); } + } + else + { + break; + } - m_Buffer.RemoveAt(i); - consumedCount++; - m_LifetimeConsumedCount++; + if (!InterpolateState.Target.HasValue) + { + break; } + previousItem = potentialItem; } } } /// - /// Convenience version of 'Update' mainly for testing - /// the reason we don't want to always call this version is so that on the calling side we can compute - /// the renderTime once for the many things being interpolated (and the many interpolators per object) + /// Interpolation Update to use when smooth dampening is enabled on a . /// - /// time since call - /// current server time + /// + /// Alternate recommended interpolation when is enabled.
+ /// This can provide a precise interpolation result between the current and target values at the expense of not being as smooth as then doulbe Lerp approach. + ///
+ /// The last frame time that is either for non-rigidbody motion and when using ridigbody motion. + /// The tick latency in relative local time. + /// The minimum time delta between the current and target value. + /// The maximum time delta between the current and target value. /// The newly interpolated value of type 'T' - public T Update(float deltaTime, NetworkTime serverTime) + internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) { - return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); + TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); + // Only interpolate when there is a start and end point and we have not already reached the end value + if (InterpolateState.Target.HasValue) + { + InterpolateState.AddDeltaTime(deltaTime); + + // Smooth dampen our current time + var current = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); + // Smooth dampen a predicted time based on our average delta time + var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + (InterpolateState.AverageDeltaTime * 2)); + // Lerp between the current and predicted. + // Note: Since smooth dampening cannot over shoot, both current and predict will eventually become the same or will be very close to the same. + // Upon stopping motion, the final resing value should be a very close aproximation of the authority side. + InterpolateState.CurrentValue = Interpolate(current, predict, deltaTime); + } + m_NbItemsReceivedThisFrame = 0; + return InterpolateState.CurrentValue; + } + #endregion + + #region Lerp Interpolation + /// + /// TryConsumeFromBuffer: Lerping Version + /// + /// + /// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern. + /// + /// + /// + private void TryConsumeFromBuffer(double renderTime, double serverTime) + { + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime)) + { + BufferedItem? previousItem = null; + var alreadyHasBufferItem = false; + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) + { + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } + + if ((potentialItem.TimeSent <= serverTime) && + (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + { + if (m_BufferQueue.TryDequeue(out BufferedItem target)) + { + if (!InterpolateState.Target.HasValue) + { + InterpolateState.Target = target; + alreadyHasBufferItem = true; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.StartTime = target.TimeSent; + InterpolateState.EndTime = target.TimeSent; + } + else + { + if (!alreadyHasBufferItem) + { + alreadyHasBufferItem = true; + InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + } + InterpolateState.EndTime = target.TimeSent; + InterpolateState.Target = target; + } + InterpolateState.ResetDelta(); + } + } + else + { + break; + } + + if (!InterpolateState.Target.HasValue) + { + break; + } + previousItem = potentialItem; + } + } } /// - /// Call to update the state of the interpolators before reading out + /// Call to update the state of the interpolators using Lerp. /// + /// + /// This approah uses double lerping which can result in an over-smoothed result. + /// /// time since last call /// our current time /// current server time @@ -195,36 +451,19 @@ public T Update(float deltaTime, NetworkTime serverTime) public T Update(float deltaTime, double renderTime, double serverTime) { TryConsumeFromBuffer(renderTime, serverTime); - - if (InvalidState) - { - throw new InvalidOperationException("trying to update interpolator when no data has been added to it yet"); - } - - // Interpolation example to understand the math below - // 4 4.5 6 6.5 - // | | | | - // A render B Server - - if (m_LifetimeConsumedCount >= 1) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements + // Only interpolate when there is a start and end point and we have not already reached the end value + if (InterpolateState.Target.HasValue) { + // The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact + // this specific behavior. float t = 1.0f; - double range = m_EndTimeConsumed - m_StartTimeConsumed; + double range = InterpolateState.EndTime - InterpolateState.StartTime; if (range > k_SmallValue) { - var rangeFactor = 1.0f / (float)range; - - t = ((float)renderTime - (float)m_StartTimeConsumed) * rangeFactor; + t = (float)((renderTime - InterpolateState.StartTime) / range); if (t < 0.0f) { - // There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed - // This clamps t to a minimum of 0 and fixes issues with longer frames and pauses - - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}"); - } t = 0.0f; } @@ -234,13 +473,37 @@ public T Update(float deltaTime, double renderTime, double serverTime) t = 1.0f; } } + var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); - var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t); - m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps + // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. + var secondLerpTime = Mathf.Clamp(deltaTime / Mathf.Max(deltaTime, MaximumInterpolationTime), deltaTime, 1.0f); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, secondLerpTime); } - m_NbItemsReceivedThisFrame = 0; - return m_CurrentInterpValue; + return InterpolateState.CurrentValue; + } + #endregion + + /// + /// Convenience version of 'Update' mainly for testing + /// the reason we don't want to always call this version is so that on the calling side we can compute + /// the renderTime once for the many things being interpolated (and the many interpolators per object) + /// + /// time since call + /// current server time + /// The newly interpolated value of type 'T' + [Obsolete("This method is being deprecated due to it being only used for internal testing purposes.", false)] + public T Update(float deltaTime, NetworkTime serverTime) + { + return UpdateInternal(deltaTime, serverTime); + } + + /// + /// Used for internal testing + /// + internal T UpdateInternal(float deltaTime, NetworkTime serverTime) + { + return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); } /// @@ -258,21 +521,25 @@ public void AddMeasurement(T newMeasurement, double sentTime) { if (m_LastBufferedItemReceived.TimeSent < sentTime) { - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); - ResetTo(newMeasurement, sentTime); + // Clear the interpolator + Clear(); + // Reset to the new value but don't automatically add the measurement (prevents recursion) + InternalReset(newMeasurement, sentTime, m_IsAngularValue, false); + m_LastMeasurementAddedTime = sentTime; + m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues - m_Buffer.Add(m_LastBufferedItemReceived); + m_BufferQueue.Enqueue(m_LastBufferedItemReceived); } - return; } - // Part the of reason for disabling extrapolation is how we add and use measurements over time. - // TODO: Add detailed description of this area in Jira ticket - if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now + // Drop measurements that are received out of order/late + if (sentTime > m_LastMeasurementAddedTime || m_BufferCount == 0) { - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); - m_Buffer.Add(m_LastBufferedItemReceived); + m_BufferCount++; + m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); + m_BufferQueue.Enqueue(m_LastBufferedItemReceived); + m_LastMeasurementAddedTime = sentTime; } } @@ -282,7 +549,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) /// The current interpolated value of type 'T' public T GetInterpolatedValue() { - return m_CurrentInterpValue; + return InterpolateState.CurrentValue; } /// @@ -302,166 +569,72 @@ public T GetInterpolatedValue() /// The time value used to interpolate between start and end values (pos) /// The interpolated value protected abstract T InterpolateUnclamped(T start, T end, float time); - } - /// - /// - /// This is a buffered linear interpolator for a type value - /// - public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator - { - /// - protected override float InterpolateUnclamped(float start, float end, float time) - { - // Disabling Extrapolation: - // TODO: Add Jira Ticket - return Mathf.Lerp(start, end, time); - } - - /// - protected override float Interpolate(float start, float end, float time) - { - return Mathf.Lerp(start, end, time); - } - } - /// - /// - /// This is a buffered linear interpolator for a type value - /// - public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator - { /// - /// Use when . - /// Use when + /// An alternate smoothing method to Lerp. /// /// - /// When using half precision (due to the imprecision) using is - /// less processor intensive (i.e. precision is already "imprecise"). - /// When using full precision (to maintain precision) using is - /// more processor intensive yet yields more precise results. + /// Not public API ready yet. /// - public bool IsSlerp; - - /// - protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) - { - if (IsSlerp) - { - return Quaternion.Slerp(start, end, time); - } - else - { - return Quaternion.Lerp(start, end, time); - } - } - - /// - protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) + /// Current item value. + /// Target item value. + /// The velocity of change. + /// Total time to smooth between the and . + /// The increasing delta time from when start to finish. + /// Maximum rate of change per pass. + /// The smoothed value. + private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) { - if (IsSlerp) - { - return Quaternion.Slerp(start, end, time); - } - else - { - return Quaternion.Lerp(start, end, time); - } + return target; } - private Quaternion ConvertToNewTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) - { - if (inLocalSpace) - { - return Quaternion.Inverse(transform.rotation) * rotation; - - } - else - { - return transform.rotation * rotation; - } - } - - protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) - { - for (int i = 0; i < m_Buffer.Count; i++) - { - var entry = m_Buffer[i]; - entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer[i] = entry; - } - - m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); - m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); - - base.OnConvertTransformSpace(transform, inLocalSpace); - } - } - - /// - /// A implementation. - /// - public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator - { /// - /// Use when . - /// Use when + /// Determines if two values of type are close to the same value. /// - public bool IsSlerp; - /// - protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time) + /// + /// Not public API ready yet. + /// + /// First value of type . + /// Second value of type . + /// The precision of the aproximation. + /// true if the two values are aproximately the same and false if they are not + private protected virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) { - if (IsSlerp) - { - return Vector3.Slerp(start, end, time); - } - else - { - return Vector3.Lerp(start, end, time); - } + return false; } - /// - protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) - { - if (IsSlerp) - { - return Vector3.Slerp(start, end, time); - } - else - { - return Vector3.Lerp(start, end, time); - } - } - - private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) + /// + /// Converts a value of type from world to local space or vice versa. + /// + /// Reference transform. + /// The item to convert. + /// local or world space (true or false). + /// The converted value. + protected internal virtual T OnConvertTransformSpace(Transform transform, T item, bool inLocalSpace) { - if (inLocalSpace) - { - return transform.InverseTransformPoint(position); - - } - else - { - return transform.TransformPoint(position); - } + return default; } - protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) + /// + /// Invoked by when the transform has transitioned between local to world or vice versa. + /// + /// The transform that the is associated with. + /// Whether the is now being tracked in local or world spaced. + internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) { - for (int i = 0; i < m_Buffer.Count; i++) + var count = m_BufferQueue.Count; + for (int i = 0; i < count; i++) { - var entry = m_Buffer[i]; - entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer[i] = entry; + var entry = m_BufferQueue.Dequeue(); + entry.Item = OnConvertTransformSpace(transform, entry.Item, inLocalSpace); + m_BufferQueue.Enqueue(entry); } - - m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); - m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); - - base.OnConvertTransformSpace(transform, inLocalSpace); + InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); + var end = InterpolateState.Target.Value; + end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.Target = end; + InLocalSpace = inLocalSpace; } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs new file mode 100644 index 0000000000..9d3765fb40 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -0,0 +1,42 @@ +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// + /// This is a buffered linear interpolator for a type value + /// + public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator + { + /// + protected override float InterpolateUnclamped(float start, float end, float time) + { + return Mathf.LerpUnclamped(start, end, time); + } + + /// + protected override float Interpolate(float start, float end, float time) + { + return Mathf.Lerp(start, end, time); + } + + /// + private protected override bool IsAproximately(float first, float second, float precision = 1E-07F) + { + return Mathf.Approximately(first, second); + } + + /// + private protected override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + { + if (m_IsAngularValue) + { + return Mathf.SmoothDampAngle(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); + } + else + { + return Mathf.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta new file mode 100644 index 0000000000..e7fb84d836 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 46f1c3f1bf9520b4581097f389f1612e \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs new file mode 100644 index 0000000000..8eb6d29bcc --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -0,0 +1,82 @@ +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// + /// This is a buffered linear interpolator for a type value + /// + public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator + { + /// + /// Use when . + /// Use when + /// + /// + /// When using half precision (due to the imprecision) using is + /// less processor intensive (i.e. precision is already "imprecise"). + /// When using full precision (to maintain precision) using is + /// more processor intensive yet yields more precise results. + /// + public bool IsSlerp; + + /// + protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) + { + if (IsSlerp) + { + return Quaternion.SlerpUnclamped(start, end, time); + } + else + { + return Quaternion.LerpUnclamped(start, end, time); + } + } + + /// + protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) + { + if (IsSlerp) + { + return Quaternion.Slerp(start, end, time); + } + else + { + return Quaternion.Lerp(start, end, time); + } + } + + /// + private protected override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Quaternion rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + { + Vector3 currentEuler = current.eulerAngles; + Vector3 targetEuler = target.eulerAngles; + for (int i = 0; i < 3; i++) + { + var velocity = rateOfChange[i]; + currentEuler[i] = Mathf.SmoothDampAngle(currentEuler[i], targetEuler[i], ref velocity, duration, maxSpeed, deltaTime); + rateOfChange[i] = velocity; + } + return Quaternion.Euler(currentEuler); + } + + /// + private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) + { + return (1.0f - Quaternion.Dot(first, second)) <= precision; + } + + /// + protected internal override Quaternion OnConvertTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) + { + if (inLocalSpace) + { + return Quaternion.Inverse(transform.rotation) * rotation; + } + else + { + return transform.rotation * rotation; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta new file mode 100644 index 0000000000..b064df1cc6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b8ad8124b66f0d54d871841d09fc176b \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs new file mode 100644 index 0000000000..2bb32d5402 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -0,0 +1,77 @@ +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// A implementation. + /// + public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator + { + /// + /// Use when . + /// Use when + /// + public bool IsSlerp; + /// + protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time) + { + if (IsSlerp) + { + return Vector3.SlerpUnclamped(start, end, time); + } + else + { + return Vector3.LerpUnclamped(start, end, time); + } + } + + /// + protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) + { + if (IsSlerp) + { + return Vector3.Slerp(start, end, time); + } + else + { + return Vector3.Lerp(start, end, time); + } + } + + /// + protected internal override Vector3 OnConvertTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) + { + if (inLocalSpace) + { + return transform.InverseTransformPoint(position); + + } + else + { + return transform.TransformPoint(position); + } + } + + /// + private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) + { + return Vector3.Distance(first, second) <= precision; + } + + /// + private protected override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) + { + if (m_IsAngularValue) + { + current.x = Mathf.SmoothDampAngle(current.x, target.x, ref rateOfChange.x, duration, maxSpeed, deltaTime); + current.y = Mathf.SmoothDampAngle(current.y, target.y, ref rateOfChange.y, duration, maxSpeed, deltaTime); + current.z = Mathf.SmoothDampAngle(current.z, target.z, ref rateOfChange.z, duration, maxSpeed, deltaTime); + return current; + } + else + { + return Vector3.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta new file mode 100644 index 0000000000..311a618f17 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e7fdc0f62b12c749bab58b047203e12 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs index 2ed982d3e3..1a112991db 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs @@ -27,9 +27,7 @@ public struct NetworkDeltaPosition : INetworkSerializable internal bool CollapsedDeltaIntoBase; - /// - /// The serialization implementation of - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { if (!SynchronizeBase) @@ -105,6 +103,7 @@ public Vector3 GetFullPosition() /// /// Only applies to the authoritative side for instances. /// + /// Returns the half float version of the current delta position. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetConvertedDelta() { @@ -120,6 +119,7 @@ public Vector3 GetConvertedDelta() /// Precision loss adjustments are one network tick behind on the /// non-authoritative side. /// + /// The full precision delta position value as a . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetDeltaPosition() { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7b2126b39d..d0951c6cfb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; using System.Text; using Unity.Mathematics; -using Unity.Netcode.Transports.UTP; using UnityEngine; namespace Unity.Netcode.Components @@ -421,6 +420,7 @@ internal set /// UseUnreliableDeltas is enabled. When set, the entire transform will /// be or has been synchronized. /// + /// true or false as to whether this state update was an unreliable frame synchronization. public bool IsUnreliableFrameSync() { return UnreliableFrameSync; @@ -433,6 +433,7 @@ public bool IsUnreliableFrameSync() /// /// Unreliable delivery will only be used if is set. /// + /// true or false as to whether this state update was sent with reliable delivery. public bool IsReliableStateUpdate() { return ReliableSequenced; @@ -616,9 +617,7 @@ public int GetNetworkTick() internal HalfVector3 HalfEulerRotation; - /// - /// Serializes this - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { // Used to calculate the LastSerializedSize value @@ -661,7 +660,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. BytePacker.WriteValueBitPacked(m_Writer, NetworkTick); - } else { @@ -937,11 +935,128 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endregion #region PROPERTIES AND GENERAL METHODS + + /// + /// The different interpolation types used with to help smooth interpolation results. + /// + public enum InterpolationTypes + { + /// + /// Uses lerping and yields a linear progression between two values. + /// + /// + /// For more information:
+ /// -
+ /// -
+ /// -
+ ///
+ Lerp, + /// + /// Uses a smooth dampening approach for interpolating between two data points and adjusts based on rate of change. + /// + /// + /// Unlike , there are no additional values needed to be adjusted for this interpolation type. + /// + SmoothDampening + } + + /// + /// The position interpolation type to use for the instance. + /// + /// + /// - yields a traditional linear result.
+ /// - adjusts based on the rate of change.
+ /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
+ /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ ///
+ [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] + public InterpolationTypes PositionInterpolationType; + + /// + /// The rotation interpolation type to use for the instance. + /// + /// + /// - yields a traditional linear result.
+ /// - adjusts based on the rate of change.
+ /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
+ /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ ///
+ [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] + public InterpolationTypes RotationInterpolationType; + + /// + /// The scale interpolation type to use for the instance. + /// + /// + /// - yields a traditional linear result.
+ /// - adjusts based on the rate of change.
+ /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
+ /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ ///
+ [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] + public InterpolationTypes ScaleInterpolationType; + + /// + /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + ///
+ /// + /// - Only used When is enabled and using .
+ /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + ///
+ [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Range(0.01f, 1.0f)] + public float PositionMaxInterpolationTime = 0.1f; + + /// + /// The rotation interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + ///
+ /// + /// - Only used When is enabled and using .
+ /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + ///
+ [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Range(0.01f, 1.0f)] + public float RotationMaxInterpolationTime = 0.1f; + + /// + /// The scale interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + ///
+ /// + /// - Only used When is enabled and using .
+ /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + ///
+ [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Range(0.01f, 1.0f)] + public float ScaleMaxInterpolationTime = 0.1f; + + /// + /// Determines if the server or client owner pushes transform states. + /// public enum AuthorityModes { + /// + /// Server pushes transform state updates + /// Server, + /// + /// Client owner pushes transform state updates. + /// Owner, } + + /// + /// Determines whether this instance will have state updates pushed by the server or the client owner. + /// + /// #if MULTIPLAYER_SERVICES_SDK_INSTALLED [Tooltip("Selects who has authority (sends state updates) over the transform. When the network topology is set to distributed authority, this always defaults to owner authority. If server (the default), then only server-side adjustments to the " + "transform will be synchronized with clients. If owner (or client), then only the owner-side adjustments to the transform will be synchronized with both the server and other clients.")] @@ -951,7 +1066,6 @@ public enum AuthorityModes #endif public AuthorityModes AuthorityMode; - /// /// When enabled, any parented s (children) of this will be forced to synchronize their transform when this instance sends a state update.
/// This can help to reduce out of sync updates that can lead to slight jitter between a parent and its child/children. @@ -1049,7 +1163,7 @@ public enum AuthorityModes /// public bool SyncPositionZ = true; - private bool SynchronizePosition + internal bool SynchronizePosition { get { @@ -1084,7 +1198,7 @@ private bool SynchronizePosition /// public bool SyncRotAngleZ = true; - private bool SynchronizeRotation + internal bool SynchronizeRotation { get { @@ -1116,7 +1230,7 @@ private bool SynchronizeRotation /// public bool SyncScaleZ = true; - private bool SynchronizeScale + internal bool SynchronizeScale { get { @@ -1211,7 +1325,14 @@ private bool SynchronizeScale /// public bool SwitchTransformSpaceWhenParented = false; + /// + /// Returns true if position is currently in local space and false if it is in world space. + /// protected bool PositionInLocalSpace => (!SwitchTransformSpaceWhenParented && InLocalSpace) || (m_PositionInterpolator != null && m_PositionInterpolator.InLocalSpace && SwitchTransformSpaceWhenParented); + + /// + /// Returns true if rotation is currently in local space and false if it is in world space. + /// protected bool RotationInLocalSpace => (!SwitchTransformSpaceWhenParented && InLocalSpace) || (m_RotationInterpolator != null && m_RotationInterpolator.InLocalSpace && SwitchTransformSpaceWhenParented); /// @@ -1474,16 +1595,12 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) #region ONSYNCHRONIZE - /// - /// This is invoked when a new client joins (server and client sides) - /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState) - /// Client Side: Adds the interpolated state which applies the NetworkTransformState as well - /// + /// /// - /// If a derived class overrides this, then make sure to invoke this base method! + /// This is invoked when a new client joins (server and client sides). + /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState). + /// Client Side: Adds the interpolated state which applies the NetworkTransformState as well. /// - /// The serializer type for buffer operations - /// The buffer serializer used for network state synchronization protected override void OnSynchronize(ref BufferSerializer serializer) { var targetClientId = m_TargetIdBeingSynchronized; @@ -1785,11 +1902,13 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var positionThreshold = Vector3.one * PositionThreshold; var rotationThreshold = Vector3.one * RotAngleThreshold; - if (m_UseRigidbodyForMotion) - { - positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); - rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); - } + // NSS: Disabling this for the time being + // TODO: Determine if we actually need this and if not remove this from NetworkRigidBodyBase + //if (m_UseRigidbodyForMotion) + //{ + // positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); + // rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); + //} #else var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; @@ -1808,9 +1927,15 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (InLocalSpace != networkState.InLocalSpace) #endif { + // When SwitchTransformSpaceWhenParented is set we automatically set our local space based on whether + // we are parented or not. networkState.InLocalSpace = SwitchTransformSpaceWhenParented ? transform.parent != null : InLocalSpace; isDirty = true; + // If SwitchTransformSpaceWhenParented is not set, then we will want to teleport networkState.IsTeleportingNextFrame = !SwitchTransformSpaceWhenParented; + // Otherwise, if SwitchTransformSpaceWhenParented is set we force a full state update. + // If interpolation is enabled, then any non-authority instance will update any pending + // buffered values to the correct world or local space values. forceState = SwitchTransformSpaceWhenParented; } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D @@ -2236,6 +2361,9 @@ internal void UpdatePositionInterpolator(Vector3 position, double time, bool res internal bool LogMotion; + /// + /// Virtual method invoked on the non-authority side after a new state has been received and applied. + /// protected virtual void OnTransformUpdated() { @@ -2849,6 +2977,9 @@ protected virtual void OnNetworkTransformStateUpdated(ref NetworkTransformState } + /// + /// Virtual method that is invoked on the non-authority side when a state update has been recieved but not yet applied. + /// protected virtual void OnBeforeUpdateTransformState() { @@ -2874,7 +3005,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf } // Get the time when this new state was sent - newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time; + newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkTickSystem.TickRate, newState.NetworkTick).Time; if (LogStateUpdate) { @@ -3130,12 +3261,24 @@ protected internal override void InternalOnNetworkPostSpawn() base.InternalOnNetworkPostSpawn(); } + /// + /// For testing purposes to quickly change the default from Lerp to SmoothDamp + /// + internal static bool AssignDefaultInterpolationType; + internal static InterpolationTypes DefaultInterpolationType; + /// /// Create interpolators when first instantiated to avoid memory allocations if the /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) /// protected virtual void Awake() { + if (AssignDefaultInterpolationType) + { + PositionInterpolationType = DefaultInterpolationType; + RotationInterpolationType = DefaultInterpolationType; + ScaleInterpolationType = DefaultInterpolationType; + } // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); m_PositionInterpolator = new BufferedLinearInterpolatorVector3(); @@ -3395,6 +3538,7 @@ public override void OnGainedOwnership() base.OnGainedOwnership(); } + /// protected override void OnOwnershipChanged(ulong previous, ulong current) { // If we were the previous owner or the newly assigned owner then reinitialize @@ -3684,49 +3828,87 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #region UPDATES AND AUTHORITY CHECKS private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; + + // Non-Authority private void UpdateInterpolation() { - // Non-Authority - if (Interpolate) - { - AdjustForChangeInTransformSpace(); + AdjustForChangeInTransformSpace(); - var serverTime = m_CachedNetworkManager.ServerTime; - var cachedServerTime = serverTime.Time; - // var offset = (float)serverTime.TickOffset; + var cachedServerTime = m_CachedNetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; + var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; #else - var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; + var cachedDeltaTime = Time.deltaTime; #endif - // With owner authoritative mode, non-authority clients can lag behind - // by more than 1 tick period of time. The current "solution" for now - // is to make their cachedRenderTime run 2 ticks behind. + var tickLatency = m_CachedNetworkManager.NetworkTimeSystem.TickLatency; - // TODO: This could most likely just always be 2 - // var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; - var ticksAgo = 2; + // If using an owner authoritative motion model + if (!IsServerAuthoritative()) + { + // and if we are in a client-server topology (including DAHost) + if (!m_CachedNetworkManager.DistributedAuthorityMode || + (m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.CMBServiceConnection)) + { + // If this instance belongs to another client (i.e. not the server/host), then add 1 to our tick latency. + if (!m_CachedNetworkManager.IsServer && !NetworkObject.IsOwnedByServer) + { + // Account for the 2xRTT with owner authoritative + tickLatency += 1; + } + } + } - var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; + var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; + // Smooth dampening specific: + // We clamp between tick rate and bit beyond the tick rate but not 2x tick rate (we predict 2x out) + var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; + // The 1.666667f value is a "magic" number tht lies between the FixedDeltaTime and 2 * the averaged + // frame update. Since smooth dampening is most useful for Rigidbody motion, the physics update + // frequency is roughly 60hz (59.x?) which 2x that value as frequency is typically close to 32-33ms. + // Look within the Interpolator.Update for smooth dampening to better understand the above. + var maxDeltaTime = (1.666667f * m_CachedNetworkManager.ServerTime.FixedDeltaTime); - // Now only update the interpolators for the portions of the transform being synchronized - if (SynchronizePosition) + // Now only update the interpolators for the portions of the transform being synchronized + if (SynchronizePosition) + { + if (PositionInterpolationType == InterpolationTypes.Lerp) + { + m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + else { - m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } + } - if (SynchronizeRotation) + if (SynchronizeRotation) + { + if (RotationInterpolationType == InterpolationTypes.Lerp) { + m_RotationInterpolator.MaximumInterpolationTime = RotationMaxInterpolationTime; // When using half precision Lerp towards the target rotation. // When using full precision Slerp towards the target rotation. /// m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + else + { + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } + } - if (SynchronizeScale) + if (SynchronizeScale) + { + if (ScaleInterpolationType == InterpolationTypes.Lerp) { - m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + else + { + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } } } @@ -3749,14 +3931,17 @@ public virtual void OnUpdate() } // Update interpolation - UpdateInterpolation(); + if (Interpolate) + { + UpdateInterpolation(); + } + // Apply the current authoritative state ApplyAuthoritativeState(); } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3771,8 +3956,12 @@ public virtual void OnFixedUpdate() m_NetworkRigidbodyInternal.WakeIfSleeping(); - // Update interpolation - UpdateInterpolation(); + + // Update interpolation when enabled + if (Interpolate) + { + UpdateInterpolation(); + } // Apply the current authoritative state ApplyAuthoritativeState(); @@ -3932,17 +4121,21 @@ private void UpdateTransformState() internal static float GetTickLatency(NetworkManager networkManager) { - if (s_NetworkTickRegistration.ContainsKey(networkManager)) + if (networkManager.IsListening) { - return s_NetworkTickRegistration[networkManager].TicksAgo; + return (float)(networkManager.NetworkTimeSystem.TickLatency + networkManager.LocalTime.TickOffset); } - return 0f; + return 0; } /// /// Returns the number of ticks (fractional) a client is latent relative - /// to its current RTT. + /// to its current averaged RTT. /// + /// + /// Only valid on clients. + /// + /// Returns the tick latency and local offset in seconds and as a float value. public static float GetTickLatency() { return GetTickLatency(NetworkManager.Singleton); @@ -3950,9 +4143,9 @@ public static float GetTickLatency() internal static float GetTickLatencyInSeconds(NetworkManager networkManager) { - if (s_NetworkTickRegistration.ContainsKey(networkManager)) + if (networkManager.IsListening) { - return s_NetworkTickRegistration[networkManager].TicksAgoInSeconds(); + return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency).Time; } return 0f; } @@ -3960,6 +4153,7 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager) /// /// Returns the tick latency in seconds (typically fractional) /// + /// Returns the current tick latency in seconds as a float value. public static float GetTickLatencyInSeconds() { return GetTickLatencyInSeconds(NetworkManager.Singleton); @@ -3994,40 +4188,12 @@ public void Remove() RemoveTickUpdate(m_NetworkManager); } - internal float TicksAgoInSeconds() - { - return 2 * m_TickFrequency; - // TODO: We need an RTT that updates regularly and not just when the client sends packets - // return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; - } - /// /// Invoked once per network tick, this will update any registered /// authority instances. /// private void TickUpdate() { - // TODO: We need an RTT that updates regularly and not just when the client sends packets - // if (m_UnityTransport != null) - // { - // // Determine the desired ticks ago by the RTT (this really should be the combination of the - // // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor - // // network quality so latent interpolation is going to be expected). - // var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); - // m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); - // var tickAgoSum = 0.0f; - // foreach (var tickAgo in m_TicksAgoSamples) - // { - // tickAgoSum += tickAgo; - // } - // m_PreviousTicksAgo = TicksAgo; - // TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); - // m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; - // // Get the partial tick value for when this is all calculated to provide an offset for determining - // // the relative starting interpolation point for the next update - // Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); - // } - // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before if (m_NetworkManager.ServerTime.Tick <= m_LastTick) { @@ -4042,40 +4208,11 @@ private void TickUpdate() } m_LastTick = m_NetworkManager.ServerTime.Tick; } - - - private UnityTransport m_UnityTransport; - private float m_TickFrequency; - // private float m_OffsetTickFrequency; - // private ulong m_TickInMS; - // private int m_TickSampleIndex; - private int m_TickRate; - public float TicksAgo { get; private set; } - // public float Offset { get; private set; } - // private float m_PreviousTicksAgo; - - private List m_TicksAgoSamples = new List(); - public NetworkTransformTickRegistration(NetworkManager networkManager) { m_NetworkManager = networkManager; m_NetworkTickUpdate = new Action(TickUpdate); networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate; - m_TickRate = (int)m_NetworkManager.NetworkConfig.TickRate; - m_TickFrequency = 1.0f / m_TickRate; - //// For the offset, it uses the fractional remainder of the tick to determine the offset. - //// In order to keep within tick boundaries, we increment the tick rate by 1 to assure it - //// will always be < the tick frequency. - // m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); - // m_TickInMS = (ulong)(1000 * m_TickFrequency); - // m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; - //// Fill the sample with a starting value of 1 - // for (int i = 0; i < m_TickRate; i++) - // { - // m_TicksAgoSamples.Add(1f); - // } - TicksAgo = 2f; - // m_PreviousTicksAgo = 1f; if (networkManager.IsServer) { networkManager.OnServerStopped += OnNetworkManagerStopped; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 02f9c98e7e..b2dcb94efe 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -119,6 +119,10 @@ private void OnEnable() public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true) { var rigidbody = contactEventHandler.GetRigidbody(); + if (rigidbody == null) + { + return; + } var instanceId = rigidbody.GetInstanceID(); if (register) { diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 916a35e6c4..c37c29fa75 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -45,11 +45,19 @@ public struct NetworkTime public double FixedTime => m_CachedTick * m_TickInterval; /// - /// Gets the fixed delta time. This value is based on the and stays constant. - /// Similar to There is no equivalent to . + /// Gets the fixed delta time. This value is calculated by dividing 1.0 by the and stays constant. /// + /// + /// This could result in a potential floating point precision variance on different systems.
+ /// See for a more precise value. + ///
public float FixedDeltaTime => (float)m_TickInterval; + /// + /// Gets the fixed delta time as a double. This value is calculated by dividing 1.0 by the and stays constant. + /// + public double FixedDeltaTimeAsDouble => m_TickInterval; + /// /// Gets the amount of network ticks which have passed until reaching the current time value. /// @@ -70,7 +78,7 @@ public NetworkTime(uint tickRate) Assert.IsTrue(tickRate > 0, "Tickrate must be a positive value."); m_TickRate = tickRate; - m_TickInterval = 1f / m_TickRate; // potential floating point precision issue, could result in different interval on different machines + m_TickInterval = 1.0 / m_TickRate; m_CachedTickOffset = 0; m_CachedTick = 0; m_TimeSec = 0; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index a40c88ed0a..c3fc9e8b67 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -1,5 +1,6 @@ using System; using Unity.Profiling; +using UnityEngine; namespace Unity.Netcode { @@ -35,7 +36,7 @@ public class NetworkTimeSystem #if DEVELOPMENT_BUILD || UNITY_EDITOR private static ProfilerMarker s_SyncTime = new ProfilerMarker($"{nameof(NetworkManager)}.SyncTime"); #endif - + private double m_PreviousTimeSec; private double m_TimeSec; private double m_CurrentLocalTimeOffset; private double m_DesiredLocalTimeOffset; @@ -75,13 +76,26 @@ public class NetworkTimeSystem ///
public double ServerTime => m_TimeSec + m_CurrentServerTimeOffset; + private float m_TickLatencyAverage = 2.0f; + + /// + /// The averaged latency in network ticks between a client and server. + /// + /// + /// For a distributed authority network topology, this latency is between + /// the client and the distributed authority service instance. + /// + public int TickLatency = 2; + internal double LastSyncedServerTimeSec { get; private set; } internal double LastSyncedRttSec { get; private set; } + internal double LastSyncedHalfRttSec { get; private set; } private NetworkConnectionManager m_ConnectionManager; private NetworkTransport m_NetworkTransport; private NetworkTickSystem m_NetworkTickSystem; private NetworkManager m_NetworkManager; + private double m_TickFrequency; /// /// @@ -101,6 +115,7 @@ public NetworkTimeSystem(double localBufferSec, double serverBufferSec = k_Defau ServerBufferSec = serverBufferSec; HardResetThresholdSec = hardResetThresholdSec; AdjustmentRatio = adjustmentRatio; + m_TickLatencyAverage = 2; } /// @@ -113,6 +128,7 @@ internal NetworkTickSystem Initialize(NetworkManager networkManager) m_NetworkTransport = networkManager.NetworkConfig.NetworkTransport; m_TimeSyncFrequencyTicks = (int)(k_TimeSyncFrequency * networkManager.NetworkConfig.TickRate); m_NetworkTickSystem = new NetworkTickSystem(networkManager.NetworkConfig.TickRate, 0, 0); + m_TickFrequency = 1.0 / networkManager.NetworkConfig.TickRate; // Only the server side needs to register for tick based time synchronization if (m_ConnectionManager.LocalClient.IsServer) { @@ -203,7 +219,14 @@ public static NetworkTimeSystem ServerTimeSystem() /// True if a hard reset of the time system occurred due to large time offset differences. False if normal time advancement occurred public bool Advance(double deltaTimeSec) { + m_PreviousTimeSec = m_TimeSec; m_TimeSec += deltaTimeSec; + // TODO: For client-server, we need a latency message sent by clients to tell us their tick latency + if (LastSyncedRttSec > 0.0f) + { + m_TickLatencyAverage = Mathf.Lerp(m_TickLatencyAverage, (float)((LastSyncedRttSec + deltaTimeSec) / m_TickFrequency), (float)deltaTimeSec); + TickLatency = (int)Mathf.Max(2.0f, Mathf.Round(m_TickLatencyAverage)); + } if (Math.Abs(m_DesiredLocalTimeOffset - m_CurrentLocalTimeOffset) > HardResetThresholdSec || Math.Abs(m_DesiredServerTimeOffset - m_CurrentServerTimeOffset) > HardResetThresholdSec) { @@ -243,6 +266,7 @@ public void Reset(double serverTimeSec, double rttSec) public void Sync(double serverTimeSec, double rttSec) { LastSyncedRttSec = rttSec; + LastSyncedHalfRttSec = (rttSec * 0.5d); LastSyncedServerTimeSec = serverTimeSec; var timeDif = serverTimeSec - m_TimeSec; @@ -250,7 +274,10 @@ public void Sync(double serverTimeSec, double rttSec) m_DesiredServerTimeOffset = timeDif - ServerBufferSec; // We adjust our desired local time offset to be half RTT since the delivery of // the TimeSyncMessage should only take half of the RTT time (legacy was using 1 full RTT) - m_DesiredLocalTimeOffset = timeDif + (rttSec * 0.5d) + LocalBufferSec; + m_DesiredLocalTimeOffset = timeDif + LastSyncedHalfRttSec + LocalBufferSec; } + + internal double ServerTimeOffset => m_DesiredServerTimeOffset; + internal double LocalTimeOffset => m_DesiredLocalTimeOffset; } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index b59f9093de..666183abe6 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -1,4 +1,3 @@ -using System; using NUnit.Framework; namespace Unity.Netcode.EditorTests @@ -43,29 +42,29 @@ public void NormalUsage() // too small update, nothing happens, doesn't consume from buffer yet var serverTime = new NetworkTime(k_MockTickRate, 0.01d); // t = 0.1d - interpolator.Update(.01f, serverTime); + interpolator.UpdateInternal(.01f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0f)); // consume first measurement, still can't interpolate with just one tick consumed serverTime += 1.0d; // t = 1.01 - interpolator.Update(1.0f, serverTime); + interpolator.UpdateInternal(1.0f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0f)); // consume second measurement, start to interpolate serverTime += 1.0d; // t = 2.01 - var valueFromUpdate = interpolator.Update(1.0f, serverTime); + var valueFromUpdate = interpolator.UpdateInternal(1.0f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0.01f).Within(k_Precision)); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0.01f).Within(k_Precision)); // test a second time, to make sure the get doesn't update the value Assert.That(valueFromUpdate, Is.EqualTo(interpolator.GetInterpolatedValue()).Within(k_Precision)); // continue interpolation serverTime = new NetworkTime(k_MockTickRate, 2.5d); // t = 2.5d - interpolator.Update(2.5f - 2.01f, serverTime); + interpolator.UpdateInternal(2.5f - 2.01f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0.5f).Within(k_Precision)); // check when reaching end serverTime += 0.5d; // t = 3 - interpolator.Update(0.5f, serverTime); + interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f).Within(k_Precision)); } @@ -86,26 +85,25 @@ public void OutOfOrderShouldStillWork() interpolator.AddMeasurement(2f, 2d); serverTime = new NetworkTime(k_MockTickRate, 1.5d); - interpolator.Update(1.5f, serverTime); + interpolator.UpdateInternal(1.5f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0f).Within(k_Precision)); serverTime += timeStep; // t = 2.0 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f).Within(k_Precision)); serverTime += timeStep; // t = 2.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1.5f).Within(k_Precision)); // makes sure that interpolation still continues in right direction interpolator.AddMeasurement(1, 1d); serverTime += timeStep; // t = 3 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f).Within(k_Precision)); } - [Ignore("TODO: Fix this test to still handle testing message loss without extrapolation. This is tracked in MTT-11338")] [Test] public void MessageLoss() { @@ -123,66 +121,28 @@ public void MessageLoss() // first value teleports interpolator serverTime = new NetworkTime(k_MockTickRate, 1d); - interpolator.Update(1f, serverTime); + interpolator.UpdateInternal(1f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f)); // nothing happens, not ready to consume second value yet serverTime += timeStep; // t = 1.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f)); // beginning of interpolation, second value consumed, currently at start serverTime += timeStep; // t = 2 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f)); // interpolation starts serverTime += timeStep; // t = 2.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1.5f)); serverTime += timeStep; // t = 3 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f)); - - // extrapolating to 2.5 - serverTime += timeStep; // t = 3.5d - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2.5f)); - - // next value skips to where it was supposed to be once buffer time is showing the next value - serverTime += timeStep; // t = 4 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3f)); - - // interpolation continues as expected - serverTime += timeStep; // t = 4.5 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3.5f)); - - serverTime += timeStep; // t = 5 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4f)); - - // lost time=6, extrapolating - serverTime += timeStep; // t = 5.5 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4.5f)); - - serverTime += timeStep; // t = 6.0 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(5f)); - - // misprediction - serverTime += timeStep; // t = 6.5 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(5.5f)); - - // lerp to right value - serverTime += timeStep; // t = 7.0 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.GreaterThan(6.0f)); - Assert.That(interpolator.GetInterpolatedValue(), Is.LessThanOrEqualTo(100f)); + // Since there is no extrapolation, the rest of this test was removed. } [Test] @@ -195,21 +155,21 @@ public void AddFirstMeasurement() interpolator.AddMeasurement(3f, 2d); serverTime += 1d; // t = 1 - var interpolatedValue = interpolator.Update(1f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(1f, serverTime); // when consuming only one measurement and it's the first one consumed, teleport to it Assert.That(interpolatedValue, Is.EqualTo(2f)); // then interpolation should work as usual serverTime += 1d; // t = 2 - interpolatedValue = interpolator.Update(1f, serverTime); + interpolatedValue = interpolator.UpdateInternal(1f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(2f)); serverTime += 0.5d; // t = 2.5 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(2.5f)); serverTime += 0.5d; // t = 3 - interpolatedValue = interpolator.Update(.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3f)); } @@ -223,12 +183,12 @@ public void JumpToEachValueIfDeltaTimeTooBig() interpolator.AddMeasurement(3f, 2d); serverTime += 1d; // t = 1 - var interpolatedValue = interpolator.Update(1f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(1f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(2f)); // big deltaTime, jumping to latest value serverTime += 9f; // t = 10 - interpolatedValue = interpolator.Update(8f, serverTime); + interpolatedValue = interpolator.UpdateInternal(8f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3)); } @@ -248,7 +208,7 @@ public void JumpToLastValueFromStart() // big time jump serverTime += 7d; // t = 10 - var interpolatedValue = interpolator.Update(10f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(10f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3f)); // interpolation continues as normal @@ -256,19 +216,19 @@ public void JumpToLastValueFromStart() interpolator.AddMeasurement(11f, serverTime.Time); // out of order serverTime = new NetworkTime(k_MockTickRate, 10.5d); // t = 10.5 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3f)); serverTime += 0.5d; // t = 11 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(10f)); serverTime += 0.5d; // t = 11.5 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(10.5f)); serverTime += 0.5d; // t = 12 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(11f)); } @@ -281,7 +241,7 @@ public void TestBufferSizeLimit() var serverTime = new NetworkTime(k_MockTickRate, 0d); serverTime += 1.0d; // t = 1 interpolator.AddMeasurement(-1f, serverTime.Time); - interpolator.Update(1f, serverTime); + interpolator.UpdateInternal(1f, serverTime); // max + 1 serverTime += 1.0d; // t = 2 @@ -293,7 +253,7 @@ public void TestBufferSizeLimit() // client was paused for a while, some time has past, we just got a burst of values from the server that teleported us to the last value received serverTime = new NetworkTime(k_MockTickRate, 102d); - var interpolatedValue = interpolator.Update(101f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(101f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(102)); } @@ -302,11 +262,10 @@ public void TestUpdatingInterpolatorWithNoData() { var interpolator = new BufferedLinearInterpolatorFloat(); var serverTime = new NetworkTime(k_MockTickRate, 0.0d); - // invalid case, this is undefined behaviour - Assert.Throws(() => interpolator.Update(1f, serverTime)); + var interpolatedValue = interpolator.UpdateInternal(1f, serverTime); + Assert.IsTrue(interpolatedValue == 0.0f, $"Expected the result to be 0.0f but was {interpolatedValue}!"); } - [Ignore("TODO: Fix this test to still test duplicated values without extrapolation. This is tracked in MTT-11338")] [Test] public void TestDuplicatedValues() { @@ -323,39 +282,24 @@ public void TestDuplicatedValues() // empty interpolator teleports to initial value serverTime = new NetworkTime(k_MockTickRate, 0.0d); serverTime += 1d; // t = 1 - var interp = interpolator.Update(1f, serverTime); + var interp = interpolator.UpdateInternal(1f, serverTime); Assert.That(interp, Is.EqualTo(1f)); // consume value, start interp, currently at start value serverTime += 1d; // t = 2 - interp = interpolator.Update(1f, serverTime); + interp = interpolator.UpdateInternal(1f, serverTime); Assert.That(interp, Is.EqualTo(1f)); // interp serverTime += 0.5d; // t = 2.5 - interp = interpolator.Update(0.5f, serverTime); + interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(1.5f)); // reach end serverTime += 0.5d; // t = 3 - interp = interpolator.Update(0.5f, serverTime); - Assert.That(interp, Is.EqualTo(2f)); - - // with unclamped interpolation, we continue mispredicting since the two last values are actually treated as the same. Therefore we're not stopping at "2" - serverTime += 0.5d; // t = 3.5 - interp = interpolator.Update(0.5f, serverTime); - Assert.That(interp, Is.EqualTo(2.5f)); - - serverTime += 0.5d; // t = 4 - interp = interpolator.Update(0.5f, serverTime); - Assert.That(interp, Is.EqualTo(3f)); - - // we add a measurement with an updated time - var pastServerTime = new NetworkTime(k_MockTickRate, 3.0d); - interpolator.AddMeasurement(2f, pastServerTime.Time); - - interp = interpolator.Update(0.5f, serverTime); + interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(2f)); + // Since there is no extrapolation, the rest of this test was removed. } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 3a7d0be8c7..efb1ce1244 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -4,18 +4,31 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] internal class NetworkTransformGeneral : NetworkTransformBase { - public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority) : + public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority, NetworkTransform.InterpolationTypes interpolationType) : base(testWithHost, authority, RotationCompression.None, Rotation.Euler, Precision.Full) - { } + { + NetworkTransform.AssignDefaultInterpolationType = true; + NetworkTransform.DefaultInterpolationType = interpolationType; + } protected override bool m_EnableTimeTravel => true; protected override bool m_SetupIsACoroutine => false; protected override bool m_TearDownIsACoroutine => false; + protected override void OnOneTimeTearDown() + { + m_EnableVerboseDebug = false; + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; + base.OnOneTimeTearDown(); + } + /// /// Test to verify nonAuthority cannot change the transform directly /// @@ -268,40 +281,45 @@ public void TestMultipleExplicitSetStates([Values] Interpolation interpolation) [Test] public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation) { - var interpolate = interpolation != Interpolation.EnableInterpolate; + var interpolate = interpolation == Interpolation.EnableInterpolate; m_AuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; + m_EnableVerboseDebug = true; + + m_AuthoritativeTransform.Teleport(Vector3.zero, Quaternion.identity, Vector3.one); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(Vector3.zero, Quaternion.identity.eulerAngles, Vector3.one), 800); + Assert.True(success, $"Timed out waiting for initialization to be applied!"); + // Test one parameter at a time first - var newPosition = new Vector3(125f, 35f, 65f); + var newPosition = new Vector3(55f, 35f, 65f); var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); - m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 800); Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!"); Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 800); Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!"); Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); // Test all parameters at once - newPosition = new Vector3(55f, 95f, -25f); + newPosition = new Vector3(-10f, 95f, -25f); newRotation = Quaternion.Euler(20, 5, 322); newScale = new Vector3(0.5f, 0.5f, 0.5f); m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 800); Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 7e12934a67..6418e0f23f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -1,5 +1,6 @@ using System.Collections; using NUnit.Framework; +using Unity.Netcode.Components; using UnityEngine; namespace Unity.Netcode.RuntimeTests @@ -9,29 +10,51 @@ namespace Unity.Netcode.RuntimeTests /// server and host operating modes and will test both authoritative /// models for each operating mode. /// - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + #endif - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif internal class NetworkTransformTests : NetworkTransformBase { @@ -41,9 +64,19 @@ internal class NetworkTransformTests : NetworkTransformBase /// /// Determines if we are running as a server or host /// Determines if we are using server or owner authority - public NetworkTransformTests(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) : + public NetworkTransformTests(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision, NetworkTransform.InterpolationTypes interpolation) : base(testWithHost, authority, rotationCompression, rotation, precision) - { } + { + NetworkTransform.AssignDefaultInterpolationType = true; + NetworkTransform.DefaultInterpolationType = interpolation; + } + + protected override void OnOneTimeTearDown() + { + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; + base.OnOneTimeTearDown(); + } protected override bool m_EnableTimeTravel => true; protected override bool m_SetupIsACoroutine => false; @@ -102,7 +135,7 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTran #if !MULTIPLAYER_TOOLS - private void UpdateTransformLocal(Components.NetworkTransform networkTransformTestComponent) + private void UpdateTransformLocal(NetworkTransform networkTransformTestComponent) { networkTransformTestComponent.transform.localPosition += GetRandomVector3(0.5f, 2.0f); var rotation = networkTransformTestComponent.transform.localRotation; @@ -112,7 +145,7 @@ private void UpdateTransformLocal(Components.NetworkTransform networkTransformTe networkTransformTestComponent.transform.localRotation = rotation; } - private void UpdateTransformWorld(Components.NetworkTransform networkTransformTestComponent) + private void UpdateTransformWorld(NetworkTransform networkTransformTestComponent) { networkTransformTestComponent.transform.position += GetRandomVector3(0.5f, 2.0f); var rotation = networkTransformTestComponent.transform.rotation; diff --git a/pvpExceptions.json b/pvpExceptions.json index bda7ccbfe0..e717d6626f 100644 --- a/pvpExceptions.json +++ b/pvpExceptions.json @@ -7,14 +7,6 @@ }, "PVP-151-1": { "errors": [ - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: NetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: TempNetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: GenerateDefaultNetworkPrefabs: undocumented", - "Unity.Netcode.Editor.Configuration.NetworkPrefabProcessor: DefaultNetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.NetworkPrefabsEditor: undocumented", - "Unity.Netcode.Editor.NetworkPrefabsEditor: void OnInspectorGUI(): undocumented", - "Unity.Netcode.Editor.NetcodeEditorBase: missing ", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnUpdate(): undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkSpawn(): undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkDespawn(): undocumented", @@ -26,40 +18,6 @@ "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Position: undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Rotation: undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Scale: undocumented", - "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.BufferedLinearInterpolator: m_InterpStartValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_CurrentInterpValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_InterpEndValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_Buffer: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: Item: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: TimeSent: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: .ctor(T, double): undocumented", - "Unity.Netcode.BufferedLinearInterpolatorQuaternion: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.BufferedLinearInterpolatorVector3: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetConvertedDelta(): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetDeltaPosition(): missing ", - "Unity.Netcode.Components.NetworkTransform: AuthorityMode: undocumented", - "Unity.Netcode.Components.NetworkTransform: PositionInLocalSpace: undocumented", - "Unity.Netcode.Components.NetworkTransform: RotationInLocalSpace: undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnTransformUpdated(): undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnBeforeUpdateTransformState(): undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnOwnershipChanged(ulong, ulong): undocumented", - "Unity.Netcode.Components.NetworkTransform: float GetTickLatency(): missing ", - "Unity.Netcode.Components.NetworkTransform: float GetTickLatencyInSeconds(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsUnreliableFrameSync(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsReliableStateUpdate(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: undocumented", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Server: undocumented", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Owner: undocumented", "Unity.Netcode.NetworkConfig: Prefabs: undocumented", "Unity.Netcode.NetworkConfig: NetworkTopology: undocumented", "Unity.Netcode.NetworkConfig: UseCMBService: undocumented", diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs index 3398f041a9..d4585ff802 100644 --- a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs @@ -83,10 +83,6 @@ private void Update() } } } - else - { - base.OnUpdate(); - } } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 0cae4ecb12..60cbf38082 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -34,6 +34,19 @@ namespace TestProject.RuntimeTests [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + // Smooth dampening interpolation pass + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] public class NestedNetworkTransformTests : IntegrationTestWithApproximation { private const string k_TestScene = "NestedNetworkTransformTestScene"; @@ -55,6 +68,7 @@ public class NestedNetworkTransformTests : IntegrationTestWithApproximation private Precision m_Precision; private NetworkTransform.AuthorityModes m_Authority; private NestedTickSynchronization m_NestedTickSynchronization; + private NetworkTransform.InterpolationTypes m_InterpolationType; public enum Interpolation { @@ -82,14 +96,19 @@ public enum NestedTickSynchronization } - public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) + public NestedNetworkTransformTests(NetworkTransform.InterpolationTypes interpolationType, Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) { + m_InterpolationType = interpolationType; m_Interpolation = interpolation; m_Precision = precision; m_Authority = authoritativeModel; m_NestedTickSynchronization = nestedTickSynchronization; } + public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) : + this(NetworkTransform.InterpolationTypes.Lerp, interpolation, precision, authoritativeModel, nestedTickSynchronization) + { } + public NestedNetworkTransformTests() { @@ -130,6 +149,8 @@ protected override void OnOneTimeTearDown() protected override IEnumerator OnSetup() { + NetworkTransform.AssignDefaultInterpolationType = true; + NetworkTransform.DefaultInterpolationType = m_InterpolationType; yield return WaitForConditionOrTimeOut(() => m_BaseSceneLoaded.IsValid() && m_BaseSceneLoaded.isLoaded); AssertOnTimeout($"Timed out waiting for scene {k_TestScene} to load!"); } @@ -148,6 +169,8 @@ protected override void OnInlineTearDown() { // This prevents us from trying to destroy the resource loaded m_PlayerPrefab = null; + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; } private void ConfigureNetworkTransform(IntegrationNetworkTransform networkTransform) From fae6b2f375f92fda9625077659d59aeeb987412a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 14 Mar 2025 00:27:28 -0500 Subject: [PATCH 201/236] feat: add networkmanager topology analytics [MTT-9974] (#3309) This PR creates a small footprint `NetworkManager` configuration usage analytics pass when running from within the Unity editor. It includes some additional modifications and organization to make any future analytics easier to add and write integration tests for. [MTT-9974](https://jira.unity3d.com/browse/MTT-9974) ## Changelog NA ## Testing and Documentation - Includes the `AnalyticsTests` integration test. - No documentation changes or additions were necessary. --- .../Editor/Analytics.meta | 8 + .../Editor/Analytics/AnalyticsHandler.cs | 25 ++ .../Editor/Analytics/AnalyticsHandler.cs.meta | 2 + .../Editor/Analytics/NetcodeAnalytics.cs | 229 ++++++++++++ .../Editor/Analytics/NetcodeAnalytics.cs.meta | 2 + .../Analytics/NetworkManagerAnalytics.cs | 47 +++ .../Analytics/NetworkManagerAnalytics.cs.meta | 2 + .../NetworkManagerAnalyticsHandler.cs | 12 + .../NetworkManagerAnalyticsHandler.cs.meta | 2 + .../Editor/AssemblyInfo.cs | 1 + .../Editor/NetworkManagerHelper.cs | 12 + .../Runtime/Configuration/NetworkConfig.cs | 40 +++ .../Runtime/Core/NetworkManager.cs | 328 ++++++++++++------ .../TestHelpers/Runtime/AssemblyInfo.cs | 1 + .../Runtime/NetcodeIntegrationTest.cs | 10 + .../Assets/Tests/Runtime/AnalyticsTests.cs | 202 +++++++++++ .../Tests/Runtime/AnalyticsTests.cs.meta | 2 + .../Runtime/testproject.runtimetests.asmdef | 1 + .../ProjectSettings/ProjectSettings.asset | 4 +- 19 files changed, 813 insertions(+), 117 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs create mode 100644 com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/AnalyticsTests.cs create mode 100644 testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/Analytics.meta b/com.unity.netcode.gameobjects/Editor/Analytics.meta new file mode 100644 index 0000000000..8894158a2b --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: df502b16f2458f1458f8546326dc5ef1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs new file mode 100644 index 0000000000..b366e225eb --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs @@ -0,0 +1,25 @@ +#if UNITY_EDITOR +using System; +using UnityEngine.Analytics; + +namespace Unity.Netcode.Editor +{ + internal class AnalyticsHandler : IAnalytic where T : IAnalytic.IData + { + private T m_Data; + + internal T Data => m_Data; + + public AnalyticsHandler(T data) + { + m_Data = data; + } + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + data = m_Data; + error = null; + return data != null; + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta new file mode 100644 index 0000000000..e1dbb20b47 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/AnalyticsHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3f65a17a86eb08c42be3b01b67fb6781 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs new file mode 100644 index 0000000000..4f1c4f8084 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs @@ -0,0 +1,229 @@ +#if UNITY_EDITOR +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UnityEngine.Analytics; + +namespace Unity.Netcode.Editor +{ + /// + /// Used to collection network session configuration information + /// + internal struct NetworkSessionInfo + { + public int SessionIndex; + public bool SessionStopped; + public bool WasServer; + public bool WasClient; + public bool UsedCMBService; + public string Transport; + public NetworkConfig NetworkConfig; + } + + /// + /// Netcode for GameObjects Analytics Class + /// + internal class NetcodeAnalytics : NetworkManager.NetcodeAnalytics + { + /// + /// Determines if we are running an integration test of the analytics integration + /// + internal static bool IsIntegrationTest = false; +#if ENABLE_NGO_ANALYTICS_LOGGING + internal static bool EnableLogging = true; +#else + internal static bool EnableLogging = false; +#endif + + // Preserves the analytics enabled flag + private bool m_OriginalAnalyticsEnabled; + + internal override void OnOneTimeSetup() + { + m_OriginalAnalyticsEnabled = EditorAnalytics.enabled; + // By default, we always disable analytics during integration testing + EditorAnalytics.enabled = false; + } + + internal override void OnOneTimeTearDown() + { + // Reset analytics to the original value + EditorAnalytics.enabled = m_OriginalAnalyticsEnabled; + } + + internal List AnalyticsTestResults = new List(); + + internal List RecentSessions = new List(); + /// + /// Invoked from . + /// + /// The new state. + /// The current instance when play mode was entered. + internal override void ModeChanged(PlayModeStateChange playModeState, NetworkManager networkManager) + { + switch (playModeState) + { + case PlayModeStateChange.EnteredPlayMode: + { + if (IsIntegrationTest) + { + AnalyticsTestResults.Clear(); + } + break; + } + case PlayModeStateChange.ExitingPlayMode: + { + // Update analytics + UpdateAnalytics(networkManager); + break; + } + } + } + + /// + /// Editor Only + /// Invoked when the session is started. + /// + /// The instance when the session is started. + internal override void SessionStarted(NetworkManager networkManager) + { + // If analytics is disabled and we are not running an integration test, then exit early. + if (!EditorAnalytics.enabled && !IsIntegrationTest) + { + return; + } + + var newSession = new NetworkSessionInfo() + { + SessionIndex = RecentSessions.Count, + WasClient = networkManager.IsClient, + WasServer = networkManager.IsServer, + NetworkConfig = networkManager.NetworkConfig.Copy(), + Transport = networkManager.NetworkConfig.NetworkTransport != null ? networkManager.NetworkConfig.NetworkTransport.GetType().Name : "None", + }; + RecentSessions.Add(newSession); + } + + /// + /// Editor Only + /// Invoked when the session is stopped or upon exiting play mode. + /// + /// The instance. + internal override void SessionStopped(NetworkManager networkManager) + { + // If analytics is disabled and we are not running an integration test or there are no sessions, then exit early. + if ((!EditorAnalytics.enabled && !IsIntegrationTest) || RecentSessions.Count == 0) + { + return; + } + + var lastIndex = RecentSessions.Count - 1; + var recentSession = RecentSessions[lastIndex]; + // If the session has already been finalized, then exit early. + if (recentSession.SessionStopped) + { + return; + } + recentSession.UsedCMBService = networkManager.CMBServiceConnection; + recentSession.SessionStopped = true; + RecentSessions[lastIndex] = recentSession; + } + + /// + /// Invoked from within when exiting play mode. + /// + private void UpdateAnalytics(NetworkManager networkManager) + { + // If analytics is disabled and we are not running an integration test or there are no sessions to process, then exit early. + if ((!EditorAnalytics.enabled && !IsIntegrationTest) || RecentSessions.Count == 0) + { + return; + } + + // If the NetworkManager isn't null, then make sure the last entry is marked off as stopped. + // If the last session is stopped, then SessionStopped will exit early. + if (networkManager != null) + { + SessionStopped(networkManager); + } + + // Parse through all of the recent network sessions to generate and send NetworkManager analytics + for (int i = 0; i < RecentSessions.Count; i++) + { + var networkManagerAnalytics = GetNetworkManagerAnalytics(RecentSessions[i]); + + var isDuplicate = false; + foreach (var analytics in AnalyticsTestResults) + { + // If we have any sessions with identical configurations, + // then we want to ignore those. + if (analytics.Data.Equals(networkManagerAnalytics)) + { + isDuplicate = true; + break; + } + } + + if (isDuplicate) + { + continue; + } + + // If not running an integration test, then go ahead and send the anlytics event data. + if (!IsIntegrationTest) + { + var result = EditorAnalytics.SendAnalytic(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); + if (EnableLogging && result != AnalyticsResult.Ok) + { + Debug.LogWarning($"[Analytics] Problem sending analytics: {result}"); + } + } + else + { + AnalyticsTestResults.Add(new NetworkManagerAnalyticsHandler(networkManagerAnalytics)); + } + } + + if (IsIntegrationTest && EnableLogging) + { + var count = 0; + foreach (var entry in AnalyticsTestResults) + { + entry.Data.LogAnalyticData(count); + count++; + } + } + RecentSessions.Clear(); + } + + /// + /// Generates a based on the passed in + /// + /// Represents a network session with the used NetworkManager configuration + /// + private NetworkManagerAnalytics GetNetworkManagerAnalytics(NetworkSessionInfo networkSession) + { + var multiplayerSDKInstalled = false; +#if MULTIPLAYER_SERVICES_SDK_INSTALLED + multiplayerSDKInstalled = true; +#endif + if (EnableLogging && !networkSession.SessionStopped) + { + Debug.LogWarning($"Session-{networkSession.SessionIndex} was not considered stopped!"); + } + var networkManagerAnalytics = new NetworkManagerAnalytics() + { + IsDistributedAuthority = networkSession.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, + WasServer = networkSession.WasServer, + WasClient = networkSession.WasClient, + UsedCMBService = networkSession.UsedCMBService, + IsUsingMultiplayerSDK = multiplayerSDKInstalled, + NetworkTransport = networkSession.Transport, + EnableSceneManagement = networkSession.NetworkConfig.EnableSceneManagement, + TickRate = (int)networkSession.NetworkConfig.TickRate, + }; + return networkManagerAnalytics; + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta new file mode 100644 index 0000000000..757665a130 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetcodeAnalytics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 05223af7b06843841868225a65c90fea \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs new file mode 100644 index 0000000000..9ba7e46142 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs @@ -0,0 +1,47 @@ +#if UNITY_EDITOR +using System; +using System.Text; +using UnityEngine; +using UnityEngine.Analytics; + +namespace Unity.Netcode.Editor +{ + [Serializable] + internal struct NetworkManagerAnalytics : IAnalytic.IData, IEquatable + { + public bool IsDistributedAuthority; + public bool WasServer; + public bool WasClient; + public bool UsedCMBService; + public bool IsUsingMultiplayerSDK; + public string NetworkTransport; + public bool EnableSceneManagement; + public int TickRate; + public override string ToString() + { + var message = new StringBuilder(); + message.AppendLine($"{nameof(IsDistributedAuthority)}: {IsDistributedAuthority}"); + message.AppendLine($"{nameof(WasServer)}: {WasServer}"); + message.AppendLine($"{nameof(WasClient)}: {WasClient}"); + message.AppendLine($"{nameof(UsedCMBService)}: {UsedCMBService}"); + message.AppendLine($"{nameof(IsUsingMultiplayerSDK)}: {IsUsingMultiplayerSDK}"); + message.AppendLine($"{nameof(NetworkTransport)}: {NetworkTransport}"); + message.AppendLine($"{nameof(EnableSceneManagement)}: {EnableSceneManagement}"); + message.AppendLine($"{nameof(TickRate)}: {TickRate}"); + return message.ToString(); + } + + internal void LogAnalyticData(int sessionNumber) + { + Debug.Log($"{nameof(NetworkManagerAnalytics)} Session-{sessionNumber}:\n {ToString()}"); + } + public bool Equals(NetworkManagerAnalytics other) + { + return IsDistributedAuthority == other.IsDistributedAuthority && WasServer == other.WasServer && WasClient == other.WasClient + && UsedCMBService == other.UsedCMBService && IsUsingMultiplayerSDK == other.IsUsingMultiplayerSDK + && EnableSceneManagement == other.EnableSceneManagement && TickRate == other.TickRate + && NetworkTransport.Equals(other.NetworkTransport); + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta new file mode 100644 index 0000000000..10980f86d5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalytics.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a987d18ba709af44bbe4033d29b80cb6 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs new file mode 100644 index 0000000000..7866df9621 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs @@ -0,0 +1,12 @@ +#if UNITY_EDITOR +using UnityEngine.Analytics; + +namespace Unity.Netcode.Editor +{ + [AnalyticInfo("NGO_NetworkManager", "unity.netcode", 5, 100, 1000)] + internal class NetworkManagerAnalyticsHandler : AnalyticsHandler + { + public NetworkManagerAnalyticsHandler(NetworkManagerAnalytics networkManagerAnalytics) : base(networkManagerAnalytics) { } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta new file mode 100644 index 0000000000..a088b0bb93 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/Analytics/NetworkManagerAnalyticsHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 087c955a14fef5448bcd1f7c7a95b21f \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs b/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs index f3fd38bc17..41e6599661 100644 --- a/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs +++ b/com.unity.netcode.gameobjects/Editor/AssemblyInfo.cs @@ -3,5 +3,6 @@ #if UNITY_INCLUDE_TESTS #if UNITY_EDITOR [assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")] +[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] #endif // UNITY_EDITOR #endif // UNITY_INCLUDE_TESTS diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 3138369d57..5cba906f2a 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -27,6 +27,7 @@ public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper private static void InitializeOnload() { Singleton = new NetworkManagerHelper(); + NetworkManager.NetworkManagerHelper = Singleton; EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged; EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged; @@ -224,6 +225,17 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool } return isParented; } + + internal NetcodeAnalytics NetcodeAnalytics = new NetcodeAnalytics(); + + /// + /// Directly define the interface method to keep this internal + /// + /// The instance which is derived from the abstract class. + NetworkManager.NetcodeAnalytics NetworkManager.INetworkManagerHelper.Analytics() + { + return NetcodeAnalytics; + } } #endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index fbbbf86ebb..826fb15ecf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -168,6 +168,46 @@ public class NetworkConfig [Tooltip("When enabled (default), the player prefab will automatically be spawned (client-side) upon the client being approved and synchronized.")] public bool AutoSpawnPlayerPrefabClientSide = true; +#if UNITY_EDITOR + /// + /// Creates a copy of the current + /// + /// a copy of this + internal NetworkConfig Copy() + { + var networkConfig = new NetworkConfig() + { + ProtocolVersion = ProtocolVersion, + NetworkTransport = NetworkTransport, + TickRate = TickRate, + ClientConnectionBufferTimeout = ClientConnectionBufferTimeout, + ConnectionApproval = ConnectionApproval, + EnableTimeResync = EnableTimeResync, + TimeResyncInterval = TimeResyncInterval, + EnsureNetworkVariableLengthSafety = EnsureNetworkVariableLengthSafety, + EnableSceneManagement = EnableSceneManagement, + ForceSamePrefabs = ForceSamePrefabs, + RecycleNetworkIds = RecycleNetworkIds, + NetworkIdRecycleDelay = NetworkIdRecycleDelay, + RpcHashSize = RpcHashSize, + LoadSceneTimeOut = LoadSceneTimeOut, + SpawnTimeout = SpawnTimeout, + EnableNetworkLogs = EnableNetworkLogs, + NetworkTopology = NetworkTopology, + UseCMBService = UseCMBService, + AutoSpawnPlayerPrefabClientSide = AutoSpawnPlayerPrefabClientSide, +#if MULTIPLAYER_TOOLS + NetworkMessageMetrics = NetworkMessageMetrics, +#endif + NetworkProfilingMetrics = NetworkProfilingMetrics, + }; + + return networkConfig; + } + +#endif + + #if MULTIPLAYER_TOOLS /// /// Controls whether network messaging metrics will be gathered. (defaults to true) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 9f257fb5e1..d564f0f8f3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -910,121 +910,6 @@ internal T Value internal Override PortOverride; - -#if UNITY_EDITOR - internal static INetworkManagerHelper NetworkManagerHelper; - - /// - /// Interface for NetworkManagerHelper - /// - internal interface INetworkManagerHelper - { - bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false); - void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false); - } - - internal delegate void ResetNetworkManagerDelegate(NetworkManager manager); - - internal static ResetNetworkManagerDelegate OnNetworkManagerReset; - - private void Reset() - { - OnNetworkManagerReset?.Invoke(this); - } - - protected virtual void OnValidateComponent() - { - - } - - private PackageInfo GetPackageInfo(string packageName) - { - return AssetDatabase.FindAssets("package").Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath(x) != null).Select(PackageInfo.FindForAssetPath).Where(x => x != null).First(x => x.name == packageName); - } - - internal void OnValidate() - { - if (NetworkConfig == null) - { - return; // May occur when the component is added - } - - // Do a validation pass on NetworkConfig properties - NetworkConfig.OnValidate(); - - if (GetComponentInChildren() != null) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}."); - } - } - - var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); - - // If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information - if (!activeScene.isDirty || EditorApplication.isUpdating) - { - return; - } - - // During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it - NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear(); - - var prefabs = NetworkConfig.Prefabs.Prefabs; - // Check network prefabs and assign to dictionary for quick look up - for (int i = 0; i < prefabs.Count; i++) - { - var networkPrefab = prefabs[i]; - var networkPrefabGo = networkPrefab?.Prefab; - if (networkPrefabGo == null) - { - continue; - } - - var networkObject = networkPrefabGo.GetComponent(); - if (networkObject == null) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogError($"Cannot register {NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root"); - } - - continue; - } - - { - var childNetworkObjects = new List(); - networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects); - if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)"); - } - } - } - } - - try - { - OnValidateComponent(); - } - catch (Exception ex) - { - Debug.LogException(ex); - } - } - - private void ModeChanged(PlayModeStateChange change) - { - if (IsListening && change == PlayModeStateChange.ExitingPlayMode) - { - OnApplicationQuit(); - } - } -#endif - /// /// Determines if the NetworkManager's GameObject is parented under another GameObject and /// notifies the user that this is not allowed for the NetworkManager. @@ -1285,6 +1170,9 @@ internal void Initialize(bool server) NetworkConfig.InitializePrefabs(); PrefabHandler.RegisterPlayerPrefab(); +#if UNITY_EDITOR + BeginNetworkSession(); +#endif } private enum StartType @@ -1584,6 +1472,10 @@ private void OnSceneUnloaded(Scene scene) internal void ShutdownInternal() { +#if UNITY_EDITOR + EndNetworkSession(); +#endif + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { NetworkLog.LogInfo(nameof(ShutdownInternal)); @@ -1721,5 +1613,211 @@ private void ParseCommandLineOptions() ParseArg(k_OverridePortArg, ref PortOverride); #endif } + +#if UNITY_EDITOR + internal static INetworkManagerHelper NetworkManagerHelper; + + /// + /// Interface for NetworkManagerHelper + /// + internal interface INetworkManagerHelper + { + bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false); + + void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false); + + internal NetcodeAnalytics Analytics(); + } + + internal abstract class NetcodeAnalytics + { + internal abstract void ModeChanged(PlayModeStateChange playModeState, NetworkManager networkManager); + + internal abstract void SessionStarted(NetworkManager networkManager); + + internal abstract void SessionStopped(NetworkManager networkManager); + + internal abstract void OnOneTimeSetup(); + + internal abstract void OnOneTimeTearDown(); + } + + internal delegate void ResetNetworkManagerDelegate(NetworkManager manager); + + internal static ResetNetworkManagerDelegate OnNetworkManagerReset; + + private void Reset() + { + OnNetworkManagerReset?.Invoke(this); + } + + /// + /// Invoked when validating the component. + /// + protected virtual void OnValidateComponent() + { + + } + + private PackageInfo GetPackageInfo(string packageName) + { + return AssetDatabase.FindAssets("package").Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath(x) != null).Select(PackageInfo.FindForAssetPath).Where(x => x != null).First(x => x.name == packageName); + } + + internal void OnValidate() + { + if (NetworkConfig == null) + { + return; // May occur when the component is added + } + + // Do a validation pass on NetworkConfig properties + NetworkConfig.OnValidate(); + + if (GetComponentInChildren() != null) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}."); + } + } + + var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); + + // If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information + if (!activeScene.isDirty || EditorApplication.isUpdating) + { + return; + } + + // During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it + NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear(); + + var prefabs = NetworkConfig.Prefabs.Prefabs; + // Check network prefabs and assign to dictionary for quick look up + for (int i = 0; i < prefabs.Count; i++) + { + var networkPrefab = prefabs[i]; + var networkPrefabGo = networkPrefab?.Prefab; + if (networkPrefabGo == null) + { + continue; + } + + var networkObject = networkPrefabGo.GetComponent(); + if (networkObject == null) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogError($"Cannot register {NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root"); + } + + continue; + } + + { + var childNetworkObjects = new List(); + networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects); + if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)"); + } + } + } + } + + try + { + OnValidateComponent(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + + internal void ModeChanged(PlayModeStateChange playModeState) + { + if (playModeState == PlayModeStateChange.ExitingPlayMode) + { + if (IsListening) + { + OnApplicationQuit(); + } + } + try + { + NetworkManagerHelper?.Analytics()?.ModeChanged(playModeState, this); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + + } + + /// + /// Invoked when NetworkManager is started. + /// + private void BeginNetworkSession() + { + try + { + NetworkManagerHelper?.Analytics()?.SessionStarted(this); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + + /// + /// Invoked when NetworkManager is stopped or upon exiting play mode. + /// + private void EndNetworkSession() + { + try + { + NetworkManagerHelper?.Analytics()?.SessionStopped(this); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } +#endif + +#if UNITY_INCLUDE_TESTS + /// + /// Used for integration tests + /// + internal static void OnOneTimeSetup() + { +#if UNITY_EDITOR + try + { + NetworkManagerHelper?.Analytics()?.OnOneTimeSetup(); + } + catch { } +#endif + } + + /// + /// Used for integration tests + /// + internal static void OnOneTimeTearDown() + { +#if UNITY_EDITOR + try + { + NetworkManagerHelper?.Analytics()?.OnOneTimeTearDown(); + } + catch { } +#endif + } +#endif + } } diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs index b21e378297..e880ab216e 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/AssemblyInfo.cs @@ -4,6 +4,7 @@ [assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] [assembly: InternalsVisibleTo("TestProject.RuntimeTests")] #if UNITY_EDITOR +[assembly: InternalsVisibleTo("Unity.Netcode.Editor")] [assembly: InternalsVisibleTo("TestProject.EditorTests")] #endif // UNITY_EDITOR #if MULTIPLAYER_TOOLS diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index fc68b71b8b..95bf441e4d 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -301,6 +301,12 @@ public void OneTimeSetup() // Enable NetcodeIntegrationTest auto-label feature NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(true); + +#if UNITY_INCLUDE_TESTS + // Provide an external hook to be able to make adjustments to netcode classes prior to running any tests + NetworkManager.OnOneTimeSetup(); +#endif + OnOneTimeSetup(); VerboseDebug($"Exiting {nameof(OneTimeSetup)}"); @@ -1293,6 +1299,10 @@ public void OneTimeTearDown() UnloadRemainingScenes(); VerboseDebug($"Exiting {nameof(OneTimeTearDown)}"); +#if UNITY_INCLUDE_TESTS + // Provide an external hook to be able to make adjustments to netcode classes after running tests + NetworkManager.OnOneTimeTearDown(); +#endif IsRunning = false; } diff --git a/testproject/Assets/Tests/Runtime/AnalyticsTests.cs b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs new file mode 100644 index 0000000000..3db6bc103f --- /dev/null +++ b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs @@ -0,0 +1,202 @@ +#if UNITY_EDITOR +using System.Collections; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.Editor; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + /// + /// In-Editor only test
+ /// Validates the analytics event data collection process.
+ /// + ///
+ internal class AnalyticsTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + private NetcodeAnalytics m_NetcodeAnalytics; + + protected override IEnumerator OnSetup() + { + NetcodeAnalytics.IsIntegrationTest = true; + m_NetcodeAnalytics = Unity.Netcode.Editor.NetworkManagerHelper.Singleton.NetcodeAnalytics; + yield return base.OnSetup(); + } + + protected override IEnumerator OnTearDown() + { + NetcodeAnalytics.IsIntegrationTest = false; + yield return base.OnTearDown(); + } + + private bool m_NetworkManagerStarted; + private bool m_NetworkManagerStopped; + private IEnumerator StartAndStopSession(int expectedCount, bool isHost, bool isDistributedAuthority) + { + m_NetworkManagerStarted = false; + m_NetworkManagerStopped = false; + + m_ServerNetworkManager.NetworkConfig.NetworkTopology = isDistributedAuthority ? NetworkTopologyTypes.DistributedAuthority : NetworkTopologyTypes.ClientServer; + + if (!isHost) + { + m_ServerNetworkManager.StartServer(); + } + else + { + m_ServerNetworkManager.StartHost(); + } + yield return WaitForConditionOrTimeOut(() => m_NetworkManagerStarted); + var serverOrHost = isHost ? "Host" : "Server"; + AssertOnTimeout($"Failed to start {nameof(NetworkManager)} as a {serverOrHost} using a {m_ServerNetworkManager.NetworkConfig.NetworkTopology} {nameof(NetworkConfig.NetworkTopology)}!"); + + yield return StopNetworkManager(); + } + + private IEnumerator StopNetworkManager() + { + var serverOrHost = m_ServerNetworkManager.IsHost ? "Host" : "Server"; + + m_ServerNetworkManager.Shutdown(); + yield return WaitForConditionOrTimeOut(() => m_NetworkManagerStopped); + AssertOnTimeout($"Failed to stop {nameof(NetworkManager)} as a {serverOrHost} using a {m_ServerNetworkManager.NetworkConfig.NetworkTopology} {nameof(NetworkConfig.NetworkTopology)}!"); + } + + private void OnServerStarted() + { + m_NetworkManagerStarted = true; + } + + private void OnServerStopped(bool wasHost) + { + m_NetworkManagerStopped = true; + } + + /// + /// Validates the NGO analytics gathering process:
+ /// - When entering play mode any previous analytics events should be cleared.
+ /// - Each session while in play mode should be tracked (minimal processing).
+ /// - When exiting play mode the sessions should be processed and cleared.
+ /// - There should only be one unique analytic data event per unique session configuration.
+ ///
+ [UnityTest] + public IEnumerator ValidateCollectionProcess() + { + var expectedCount = 0; + var currentCount = 0; + m_ServerNetworkManager.OnServerStarted += OnServerStarted; + m_ServerNetworkManager.OnServerStopped += OnServerStopped; + m_PlayerPrefab.SetActive(false); + m_ServerNetworkManager.NetworkConfig.PlayerPrefab = null; + yield return StopNetworkManager(); + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == 1, $"Expected 1 session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == 0, $"Expected 0 analytics events but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + + // Simulate exiting play mode + m_NetcodeAnalytics.ModeChanged(UnityEditor.PlayModeStateChange.ExitingPlayMode, m_ServerNetworkManager); + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == 1, $"Expected 1 analytics event but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + + // Simulate entering play mode + m_NetcodeAnalytics.ModeChanged(UnityEditor.PlayModeStateChange.EnteredPlayMode, m_ServerNetworkManager); + + Assert.IsFalse(m_ServerNetworkManager.IsListening, $"Networkmanager should be stopped but is still listening!"); + // Client-Server + // Start as a Server and then shutdown + yield return StartAndStopSession(expectedCount, false, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a server again with the same settings and then shutdown + // (this should be excluded in the final analytics event data) + yield return StartAndStopSession(expectedCount, false, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a Host and then shutdown + yield return StartAndStopSession(expectedCount, true, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a DAHost and then shutdown + yield return StartAndStopSession(expectedCount, true, true); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + + // Start as a Host again and then shutdown + // (this should be excluded in the final analytics event data) + yield return StartAndStopSession(expectedCount, true, false); + expectedCount++; + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == expectedCount, $"Expected {expectedCount} session but found: {m_NetcodeAnalytics.RecentSessions.Count}!"); + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == 0, $"Expected 0 analytics events but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + + ////////////////////////////////////////////////// + // Validating that each session's configuration is tracked for generating the analytics data events. + // Verify session 1 + var session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && !session.WasClient, $"Expected session to be started as a server session but it was not! Server: {session.WasServer} Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 2 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && !session.WasClient, $"Expected session to be started as a server session but it was not! Server: {session.WasServer} Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 3 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && session.WasClient, $"Expected session to be started as a host session but it was not! Server: {session.WasServer}Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 4 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer && session.WasClient, $"Expected session to be started as a host session but it was not! Server: {session.WasServer}Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, $"Expected session to be a {NetworkTopologyTypes.DistributedAuthority} session but it was {session.NetworkConfig.NetworkTopology}!"); + + // Verify session 5 + currentCount++; + session = m_NetcodeAnalytics.RecentSessions[currentCount]; + Assert.True(session.WasServer == true && session.WasClient, $"Expected session to be started as a host session but it was not! Server: {session.WasServer}Client: {session.WasClient}"); + Assert.True(session.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer, $"Expected session to be a {NetworkTopologyTypes.ClientServer} session but it was {session.NetworkConfig.NetworkTopology}!"); + + ////////////////////////////////////////////////// + // Validating that the analytics event data that would be sent should only contain information on the 3 unique session configurations + // Client-Server as a Server + // Client-Server as a Host + // Distributed Authority as a DAHost + m_NetcodeAnalytics.ModeChanged(UnityEditor.PlayModeStateChange.ExitingPlayMode, m_ServerNetworkManager); + + // Verify that we cleared out the NetcodeAnalytics.RecentSessions when we exited play mode + Assert.True(m_NetcodeAnalytics.RecentSessions.Count == 0, $"After exiting play mode we expected 0 RecentSessions but had {m_NetcodeAnalytics.RecentSessions.Count}"); + + // Adjust our expected count to be 2 less than the RecentSessions since we intentionally included two identical sessions. + // (There should only be unique session configurations included in the final analytics event data entries) + expectedCount -= 2; + + Assert.True(m_NetcodeAnalytics.AnalyticsTestResults.Count == expectedCount, $"Expected {expectedCount} analytics event but found: {m_NetcodeAnalytics.AnalyticsTestResults.Count}!"); + currentCount = 0; + // Verify event data 1 + var eventData = m_NetcodeAnalytics.AnalyticsTestResults[currentCount].Data; + Assert.True(eventData.WasServer && !eventData.WasClient, $"Expected session to be started as a server session but it was not! WasServer: {eventData.WasServer} WasClient: {eventData.WasClient}"); + Assert.True(!eventData.IsDistributedAuthority, $"Expected IsDistributedAuthority to be false but it was true!"); + + // Verify event data 2 + currentCount++; + eventData = m_NetcodeAnalytics.AnalyticsTestResults[currentCount].Data; + Assert.True(eventData.WasServer && eventData.WasClient, $"Expected session to be started as a Host session but it was not! WasServer: {eventData.WasServer} WasClient: {eventData.WasClient}"); + Assert.True(!eventData.IsDistributedAuthority, $"Expected IsDistributedAuthority to be false but it was true!"); + + // Verify event data 3 + currentCount++; + eventData = m_NetcodeAnalytics.AnalyticsTestResults[currentCount].Data; + Assert.True(eventData.WasServer && eventData.WasClient, $"Expected session to be started as a host session but it was not! WasServer: {eventData.WasServer} WasClient: {eventData.WasClient}"); + Assert.True(eventData.IsDistributedAuthority, $"Expected IsDistributedAuthority to be true but it was false!"); + yield return null; + } + } +} +#endif diff --git a/testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta new file mode 100644 index 0000000000..0075e04b38 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/AnalyticsTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 095c486606a56464ab25cccb457df562 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef index 846250639c..80fb79d2d3 100644 --- a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef +++ b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef @@ -4,6 +4,7 @@ "references": [ "Unity.Netcode.Runtime", "Unity.Netcode.RuntimeTests", + "Unity.Netcode.Editor", "TestProject.ManualTests", "TestProject", "Unity.Addressables", diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 9975ea1a15..492e94571c 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -17,8 +17,8 @@ PlayerSettings: defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} - m_ShowUnitySplashScreen: 1 - m_ShowUnitySplashLogo: 1 + m_ShowUnitySplashScreen: 0 + m_ShowUnitySplashLogo: 0 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 m_SplashScreenLogoStyle: 1 From 8f2981bb44ed256352a6acc823177996f914c3f9 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 18 Mar 2025 10:40:02 -0500 Subject: [PATCH 202/236] fix: android time test failures due to edge case float precision errors (#3351) `NetworkTimeSystemTests.CorrectAmountTicksTest` Using the double version of tick frequency to calculate number of ticks passed. `TimeInitializationTest.TestClientTimeInitializationOnConnect` Dividing the double version of tick frequency to calculate number of ticks passed and using the double version of `Math.Floor` before casting that to an integer. ## Changelog NA ## Testing and Documentation - Includes integration test updates. - No documentation changes or additions were necessary. --- .../Tests/Runtime/Timing/NetworkTimeSystemTests.cs | 3 +-- .../Tests/Runtime/Timing/TimeInitializationTest.cs | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs index c38bc86275..7172feb302 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/NetworkTimeSystemTests.cs @@ -62,7 +62,7 @@ public IEnumerator PlayerLoopTimeTest_WithDifferentTimeScale([Values(0.0f, 0.1f, public IEnumerator CorrectAmountTicksTest() { NetworkTickSystem tickSystem = NetworkManager.Singleton.NetworkTickSystem; - float delta = tickSystem.LocalTime.FixedDeltaTime; + double delta = tickSystem.LocalTime.FixedDeltaTimeAsDouble; int previous_localTickCalculated = 0; int previous_serverTickCalculated = 0; @@ -79,7 +79,6 @@ public IEnumerator CorrectAmountTicksTest() previous_localTickCalculated++; } - tickCalculated = NetworkManager.Singleton.ServerTime.Time / delta; previous_serverTickCalculated = (int)tickCalculated; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs index 5d8f39c444..dc06c2877d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Timing/TimeInitializationTest.cs @@ -42,10 +42,11 @@ public IEnumerator TestClientTimeInitializationOnConnect([Values(0, 1f)] float s yield return new WaitUntil(() => server.NetworkTickSystem.ServerTime.Tick > 2); var serverTimePassed = server.NetworkTickSystem.ServerTime.Time; - var expectedServerTickCount = Mathf.FloorToInt((float)(serverTimePassed * 30)); + // Use FixedDeltaTimeAsDouble and divide the tick frequency into the time passed to get the accurate tick count + var expectedServerTickCount = (int)System.Math.Floor(serverTimePassed / server.ServerTime.FixedDeltaTimeAsDouble); var ticksPassed = server.NetworkTickSystem.ServerTime.Tick - serverTick; - Assert.AreEqual(expectedServerTickCount, ticksPassed); + Assert.AreEqual(expectedServerTickCount, ticksPassed, $"Calculated tick failed: Tick ({expectedServerTickCount}) TicksPassed ({ticksPassed}) Server Tick ({server.NetworkTickSystem.ServerTime.Tick}) Prev-Server Tick ({serverTick})"); yield return new WaitForSeconds(clientStartDelay); From 10d5843638df71dac68a5bc88c67b0c581939b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Wed, 19 Mar 2025 13:17:31 +0100 Subject: [PATCH 203/236] docs: [NGOv2.X] Correction of API Reference link (#3353) This PR builds on https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/2579 and updates link for API Reference in **Documentation~** folder so it's up to date --- com.unity.netcode.gameobjects/Documentation~/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/index.md b/com.unity.netcode.gameobjects/Documentation~/index.md index bd5e331a18..d07cf7b5b1 100644 --- a/com.unity.netcode.gameobjects/Documentation~/index.md +++ b/com.unity.netcode.gameobjects/Documentation~/index.md @@ -9,7 +9,7 @@ See guides below to install Unity Netcode for GameObjects, set up your project, - [Documentation](https://docs-multiplayer.unity3d.com/netcode/current/about) - [Installation](https://docs-multiplayer.unity3d.com/netcode/current/installation) - [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/get-started-ngo) -- [API Reference](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.6/api/index.html) +- [API Reference](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.2/api/index.html) # Technical details From d79c74396dc11bd9836c1edaa3d5de3656ded4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 21 Mar 2025 11:55:07 +0100 Subject: [PATCH 204/236] ci: [NGOv2.X] Corrections of CI setup issues (#3329) This PR focuses on fixing simplifying CI setup by removing some unnecessary complexity and adding comments to describe CI steps 1. UTR command was corrected to use preinstalled UTR on the image instead of downloading it 2. Removed upm-ci package pack definition and converted Code Coverage test to use upm-pvp pack job 3. Simplifies some unnecessary CI commands 4. Added detailed comments regarding CI setup and commands 5. Created CI_README file which describes overall configuration structure. 6. For macOS devices we now use M1 model (Apple Silicon) in contrast to default x64 Intel Mac which introduced a lot of bit-flipping errors --------- Co-authored-by: Noel Stephens --- .yamato/README.md | 80 +++++++++++++ .yamato/_run-all.yml | 23 ++-- .yamato/_triggers.yml | 40 ++++++- .yamato/code-coverage.yml | 45 +++++-- .yamato/console-standalone-test.yml | 78 +++++++----- .yamato/desktop-standalone-tests.yml | 113 ++++++++---------- .yamato/mobile-standalone-test.yml | 101 ++++++++-------- .yamato/package-pack.yml | 54 ++++----- .yamato/package-tests.yml | 45 ++++--- .yamato/performance-tests.yml | 66 +++++----- .yamato/project-pack.yml | 30 ++++- .yamato/project-standards.yml | 47 ++++++-- .yamato/project-tests.yml | 38 ++++-- .yamato/project-updated-dependencies-test.yml | 41 +++++-- .yamato/project.metafile | 52 ++++++-- .yamato/webgl-build.yml | 70 +++++------ 16 files changed, 600 insertions(+), 323 deletions(-) create mode 100644 .yamato/README.md diff --git a/.yamato/README.md b/.yamato/README.md new file mode 100644 index 0000000000..2e0a761714 --- /dev/null +++ b/.yamato/README.md @@ -0,0 +1,80 @@ +# Netcode for GameObjects CI Documentation + +## Overview +This document provides an overview of the Continuous Integration (CI) implementation for Netcode for GameObjects. +Specifics of each test are described within related files (for example .yamato/package-tests.yml) and this file present high level overview related to overall implementation. + +## Test Configurations +CI related files are present inside .yamato/ folder and we can distinguish specific tests + +### Helper jobs +- `.yamato/package-pack.yml` responsible for generating package artifacts (.tgz) required for testing and publishing. +- `.yamato/project-pack.yml` responsible for generating package artifacts (.tgz) required for testing and publishing. This packs all packages of a given project. +- `.yamato/_run-all.yml` responsible for grouping tests into groups for easier management (for example "all console tests"). +- `.yamato/_triggers.yml` responsible for defining triggers (PR, nightly, weekly etc.) and defining which tests to run. +- `disable-burst-if-requested.py` responsible for helping to disable burst if needed. +- `.yamato/project.metafile` being the central configuration file defining test environments, platforms, projects, editors and other parameters used. + +### In-Editor Tests +- `.yamato/package-tests.yml` responsible for running package tests. +- `.yamato/project-tests.yml` responsible for running project tests. +- `.yamato/project-updated-dependencies-test.yml` responsible for running tests after updating dependencies to latest ones. +- `.yamato/project-standards.yml` responsible for running standards check on a package. +- `.yamato/code-coverage.yml` responsible for running code coverage analysis. +- `.yamato/performance-tests.yml` responsible for running performance tests. +- `.yamato/webgl-build.yml` responsible for making a build for webgl platform. + +### Standalone Tests +- `.yamato/desktop-standalone-tests.yml` responsible for running tests on standalone desktops. +- `.yamato/console-standalone-test.yml` responsible for running tests on standalone consoles. +- `.yamato/mobile-standalone-test.yml` responsible for running tests on standalone mobiles. + +### Wrench jobs +This file is auto generated by wrench and used for automating release process. +Those tests can also be used by our CI as dependencies. +- `.yamato/wrench/api-validation-jobs.yml` responsible for validating api for a given package version (for example if there are no breaking changes when updating patch version) +- `.yamato/wrench/package-pack-jobs.yml` responsible for generating package artifacts (.tgz) required for testing and publishing. +- `.yamato/wrench/preview-a-p-v.yml` responsible for validating PVP (Package Verification Profiles) standards for the package. +- `.yamato/wrench/promotion-jobs.yml` responsible for publishing. +- `.yamato/wrench/publish-trigger.yml` responsible for defining jobs that need to pass in order to publish. +- `.yamato/wrench/recipe-regeneration.yml` responsible for verifying regeneration of wrench recipes (no manual changes). +- `.yamato/wrench/validation-jobs.yml` responsible for running package tests. +- `.yamato/wrench/wrench_config.json` this is the configuration file for wrench. +- `.yamato/generated-scripts/` is a collection of infrastructure instability detection tools. + +## Jobs Configuration Structure +This section explains some configuration details that may be confusing while looking at .yml files. + +### Job Generation +Our configuration uses a dynamic job generation approach based on the `.yamato/project.metafile` configuration. While it may appear that only one job is defined per file, the system (Yamato) actually generates multiple jobs through nested loops. +Usually only 1 job is defined and then generated with multiple configurations/parameters but in case of standalone tests 2 jobs are defined which are responsible for building and then testing. + +### Job Naming Convention +Because the jobs are being generated their names will be filled by given parameters from project.metfaile. For example jobs from .yamato/console-standalone-test may have names like: +- Display Name (in Yamato): `Build testproject - [win, 6000.0, il2cpp]` +- Internal Job Name (used when adding dependency for example): `console_standalone_build_testproject_win_6000.0` + +## Platform Support +Currently, the CI implementation supports the following platforms: +1. Console Platforms: + - Switch + - PS4 + - PS5 + - Xbox360 + - XboxOne +2. Mobile Platforms: + - Android + - iOS +3. Desktop Platforms: + - Windows + - Ubuntu + - macOS +4. Unity Editor Versions + - Supports NGOv2.X (6000.0+ editors) +5. Architectures + - x64 + - ARM64 (This is present for consoles/mobiles but will be extended. More information is present in specific standalone test files) + +## Design Considerations +In theory, we could manually write jobs for every configuration. However, this approach would be more error-prone, especially when modifications or fixes are needed, as it would require keeping track of all configurations. +The downside of our current approach is that it can sometimes impact readability due to the use of nested if and for statements. \ No newline at end of file diff --git a/.yamato/_run-all.yml b/.yamato/_run-all.yml index af4c78b7f0..985e1d2f30 100644 --- a/.yamato/_run-all.yml +++ b/.yamato/_run-all.yml @@ -1,6 +1,15 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- +# DESCRIPTION-------------------------------------------------------------------------- + # Those jobs group together related test as dependencies to allow to easily manage running a given set of tests. + # This enables efficient test execution for different validation scenarios + +# QUALITY CONSIDERATIONS--------------------------------------------------------------- + # It's important to ensure that all dependencies exist (this can be verified in Yamato) since a modification in parameters may result in a given job not being generated, and thus we will not be able to run such erroneous job. + +#----------------------------------------------------------------------------------- + # Runs all package tests run_all_package_tests: name: Run All Package Tests @@ -73,7 +82,7 @@ run_all_webgl_builds: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} - - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform }}_{{ editor }} + - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} @@ -86,7 +95,7 @@ run_all_webgl_builds_trunk: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.default -%} - - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform }}_{{ editor }} + - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} @@ -128,7 +137,7 @@ run_all_project_tests_mobile_standalone: {% for project in projects.default -%} {% for platform in test_platforms.mobile_test -%} {% for editor in validation_editors.all -%} - - .yamato/mobile-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} + - .yamato/mobile-standalone-test.yml#mobile_standalone_test_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} @@ -141,7 +150,7 @@ run_all_project_tests_mobile_standalone_trunk: {% for project in projects.default -%} {% for platform in test_platforms.mobile_test -%} {% for editor in validation_editors.default -%} - - .yamato/mobile-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} + - .yamato/mobile-standalone-test.yml#mobile_standalone_test_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} @@ -154,7 +163,7 @@ run_all_project_tests_console_standalone: {% for project in projects.default -%} {% for platform in test_platforms.console_test -%} {% for editor in validation_editors.all -%} - - .yamato/console-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} + - .yamato/console-standalone-test.yml#console_standalone_test_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} @@ -167,7 +176,7 @@ run_all_project_tests_console_standalone_trunk: {% for project in projects.default -%} {% for platform in test_platforms.console_test -%} {% for editor in validation_editors.default -%} - - .yamato/console-standalone-test.yml#run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }} + - .yamato/console-standalone-test.yml#console_standalone_test_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index 8abd3622cf..352f1f6c08 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -1,6 +1,44 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- +# DESCRIPTION-------------------------------------------------------------------------- + # This configuration defines three main CI trigger patterns: + # 1. Pull Request Validation: Validation performed on PR basis + # 2. Nightly Development: Test set run nightly (validates most important test cases) + # 3. Weekly Full Validation: Test set run weekly (validates all test cases to prevent any suprises) + # Each pattern represents different balance between validation depth, execution time and CI resource usage + +# TRIGGER PATTERNS------------------------------------------------------------------- + # Pull Request: + # This test validaes Standards, Package tests, Project tests and Desktop standalone tests to ensure that main platforms are covered + # Triggers on PRs to develop, develop-2.0.0, and release branches + # Focuses on critical validation paths that we should validate before merging PRs + # Cancels previous runs on new commits + # Excludes draft PRs + + # Nightly: + # This test validaes same subset as pull_request_trigger with addition of mobile/console tests and webgl builds + # Runs daily on develop-2.0.0 (local configuration) + # Includes all test types but only on trunk. TODO: Add validation for minimum supported editor in nightly + # Adds platform-specific and APV validation + + # Weekly: + # This test validaes same subset as develop_nightly but runs per all supported editors as well as executes code coverage test and runs project standards per project + # Runs across all supported editor versions + # Includes code coverage analysis + # Validates all projects and standards + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs configurations are generated by ensuring that all dependencies are successful. + # The dependencies are taken from _run-all.yml file where we can gather multiple tests into proper sets + +# QUALITY CONSIDERATIONS--------------------------------------------------------------- +# It's important to ensure that all dependencies exist (this can be verified in Yamato) since a modification in parameters may result in a given job not being generated, and thus we will not be able to run such erroneous job. + + + +#----------------------------------------------------------------------------------- + # Run all relevant tasks when a pull request targeting the develop or release branch is created or updated. pull_request_trigger: name: Pull Request Trigger (develop, develop-2.0.0, & release branches) diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index c0aa7e59fd..ced6277452 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -1,10 +1,31 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- + +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for executing package tests with code coverage analysis enabled. + # Coverage analysis provides insights into: + # Test coverage metrics for NGO assemblies + # Line and branch coverage statistics + # Generated HTML reports for coverage visualization + # Additional metrics for coverage analysis + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs are generated using nested loops through: + # 1. For default platform only (Ubuntu) since coverage would not vary between platforms (no need for checks on more platforms) + # 2. For default editor version (trunk) since coverage would not vary between editors (no need for checks on more editors) -# Runs package tests in order to determine code coverage of the NGO package. -# In essence it's performing the same task as .yamato/package-test jobs with the overhead being the measured code coverage -# It's ok for code coverage to be performed only on one platform (default) since code coverage won't change much between those. -# Default platform was chosen (ubuntu) since it's the fastest and most resource friendly with default editor. +#TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # In theory this job also runs package tests, but we don't want to use it as default since is heavier (because of added coverage analysis) and coverage is not changing that often + # Requires Unity Editor installation + # Burst compilation is disabled to ensure accurate coverage measurement + # In order to properly use -coverage-results-path parameter we need to start it with $PWD (which means the absolute path). Otherwise coverage results will not be visible + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # TODO: somewhere in 2025 we will be able to upload resuls to CodeCov from public repos + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + + + {% for platform in test_platforms.default -%} {% for editor in validation_editors.default -%} code_coverage_{{ platform.name }}_{{ editor }}: @@ -13,16 +34,18 @@ code_coverage_{{ platform.name }}_{{ editor }}: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} commands: - - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait - - DISPLAY=:0 upm-ci package test -u {{ editor }} --package-path com.unity.netcode.gameobjects --enable-code-coverage --code-coverage-options 'generateAdditionalMetrics;generateHtmlReport;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime' --extra-utr-arg="--extra-editor-arg=--burst-disable-compilation --extra-editor-arg=testCategory --extra-editor-arg=!Performance --timeout=1800 --reruncount=1 --clean-library-on-rerun" + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models + - upm-pvp create-test-project test-project --packages "upm-ci~/packages/*.tgz" --unity .Editor + - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --enable-code-coverage -coverage-results-path=$PWD/test-results/CodeCoverage --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout=1800 --reruncount=1 --clean-library-on-rerun --artifacts-path=test-results artifacts: logs: paths: - - "upm-ci~/test-results/**/*" - - "upm-ci~/test-results/CoverageResults/**/*" + - "test-results/**/*" dependencies: - - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }}_upmCI + - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} {% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/console-standalone-test.yml b/.yamato/console-standalone-test.yml index 2d5ca4861e..ee74bfee0b 100644 --- a/.yamato/console-standalone-test.yml +++ b/.yamato/console-standalone-test.yml @@ -1,35 +1,57 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Builds a player on console standalone platform and executes RuntimeTests (equivalent to PlayMode tests) of the NGO package in the Standalone build. -# Default project (testptoject) in this case is used as a context. -# Builds/Tests are made on each console platform (PS4, PS5, Switch, XboxOne, XboxSeriesX) as in project.metafile declaration -# Builds/Tests are made on each supported editor as in project.metafile declaration +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for Console platform test validation. + # Those tests cover both PlayMode and EditMode tests from package test assemblies. -# For SOME of the console devices it's necessary to split build and run phases so it was split for all -# For all consoles we need to use il2cpp scripting backend (so no testing with mono) -# Switch works only with ARM64 and the rest with x64 architectures -# For now all platforms used for building are windows based +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs are generated using nested loops (separate build phase and run phase). Worth noting that run phase uses the build as dependency: + # 1. For all console platform (Switch, ps4, ps5, xbox360, xboxOne) + # 2. For all supported Unity Editor versions (for NGOv2.X this means 6000.0+ editors) + # 3. For the default project. + +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # For console devices a split is required into two phases: + # 1. Build Phase: Creates standalone players for console platforms + # 2. Run Phase: Executes runtime tests on actual console devices + # The Run phase uses build job as dependency + + # Note: More of a Unity specific but test assemblies need to be included in the build phase command + # Note: All builds can be made on x64 machines since those are compatible with ARM64 target devices + +# PLATFORM SPECIFICS----------------------------------------------------------------- + # Common Requirements: + # All consoles require IL2CPP scripting backend + # Platform-specific SDK environment variables + + # Platform-Specific: + # Switch: ARM64 architecture only + # Other Consoles: x64 architecture + # Each console requires specific SDK paths and tools + +# QUALITY THOUGHTS-------------------------------------------------------------------- + # TODO: consider adding all projects that have tests + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + -# Builds are made with x64 architecture machines since those are compatible to run on ARM64 devices + +# BUILD PHASE CONFIGURATION------------------------------------------------------------------------------------ {% for project in projects.default -%} {% for platform in test_platforms.console_build -%} {% for editor in validation_editors.all -%} -build_{{ project.name }}_{{ platform.name }}_{{ editor }}: +console_standalone_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: name: Build {{ project.name }} - [{{ platform.name }}, {{ editor }}, il2cpp{% if platform.name == "switch" %}, arm64{% endif %}] agent: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} commands: - # Installing tools. unity-downloader-cli and utr should be already preinstalled on the image - - sudo pip install unity-downloader-cli - - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp -c {{ platform.name }} --fast --wait - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat - - # Platform specific Build - - utr --testproject={{ project.path }} --architecture={% if platform.name == "switch" %}arm64{% else %}x64{% endif %} --scripting-backend=il2cpp --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --artifacts-path=artifacts --player-save-path=build/players --testfilter="Unity.Netcode.RuntimeTests.*" --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 - + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor -c il2cpp -c {{ platform.name }} + - UnifiedTestRunner --testproject={{ project.path }} --architecture={% if platform.name == "switch" %}arm64{% else %}x64{% endif %} --scripting-backend=il2cpp --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --artifacts-path=artifacts --player-save-path=build/players --testfilter="Unity.Netcode.RuntimeTests.*" --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 variables: # PS4 related SCE_ORBIS_SDK_DIR: 'C:\Users\bokken\SCE\ps4_sdk_12_00' @@ -53,28 +75,22 @@ build_{{ project.name }}_{{ platform.name }}_{{ editor }}: -# Executes RuntimeTests of the NGO package in the Standalone build for consoles +# RUN PHASE CONFIGURATION------------------------------------------------------------------------------------ {% for project in projects.default -%} {% for platform in test_platforms.console_test -%} {% for editor in validation_editors.all -%} -run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }}: +console_standalone_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: name: Run {{ project.name }} Tests - [{{ platform.name }}, {{ editor }}, il2cpp] agent: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} {% if platform.model %} - model: {{ platform.model }} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} commands: - # Installing tools. - - sudo pip install unity-downloader-cli - - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp -c {{ platform.name }} --fast --wait - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat - - # Platform specific Execution - - utr --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --artifacts-path=test-results --player-load-path=build/players --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 - + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor -c il2cpp -c {{ platform.name }} + - UnifiedTestRunner --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --artifacts-path=test-results --player-load-path=build/players --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 variables: # PS4 related SCE_ORBIS_SDK_DIR: 'C:\Users\bokken\SCE\ps4_sdk_12_00' @@ -90,7 +106,7 @@ run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }}: paths: - "test-results/**/*" dependencies: - - .yamato/console-standalone-test.yml#build_{{ project.name }}_{{ platform.name }}_{{ editor }} + - .yamato/console-standalone-test.yml#console_standalone_build_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/desktop-standalone-tests.yml b/.yamato/desktop-standalone-tests.yml index bdacea3d49..b6539b1cbf 100644 --- a/.yamato/desktop-standalone-tests.yml +++ b/.yamato/desktop-standalone-tests.yml @@ -1,14 +1,34 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Builds a player on desktop standalone platform and executes RuntimeTests (equivalent to PlayMode tests) of the NGO package in the Standalone build. -# Default project (testptoject) in this case is used as a context. -# Builds are made on each desktop platform as in project.metafile declaration -# Builds are made on each supported editor as in project.metafile declaration -# Builds are made with different scripting backends as in project.metafile declaration -# ARM64 architectures are for now not considered since Windows_arm64 is recommended to use only after builds (would require separation here) and when it comes to macOS_arm64 there is problem with OpenCL not being available - -# Build phase +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for Desktop platform test validation. + # Those tests cover both PlayMode and EditMode tests from package test assemblies. + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs are generated using nested loops (separate build phase and run phase). Worth noting that run phase uses the build as dependency: + # 1. For all desktop platform (Windows, macOS, Ubuntu) + # 2. For all supported Unity Editor versions (for NGOv2.X this means 6000.0+ editors) + # 3. For the default project. + # 4. For all scripting backends (mono, il2cpp) + +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # For desktop devices a split is into two phases is not required but we use it for consistency with setup of others standalone platforms: + # 1. Build Phase: Creates standalone players for desktop platforms + # 2. Run Phase: Executes runtime tests on actual desktop devices + # The Run phase uses build job as dependency + + # Note: More of a Unity specific but test assemblies need to be included in the build phase command + # Note: All builds can be made on x64 machines since those are compatible with ARM64 target devices + +# QUALITY THOUGHTS-------------------------------------------------------------------- + # TODO: consider adding all projects that have tests + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + +#----------------------------------------------------------------------------------- + + +# BUILD PHASE CONFIGURATION------------------------------------------------------------------------------------ {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} @@ -19,34 +39,12 @@ desktop_standalone_build_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ type: {% if platform.name == "mac" %} {{ platform.type }} {% else %} {{ platform.type }}::GPU {% endif %} image: {{ platform.image }} flavor: {{ platform.flavor }} - commands: -# Installing tools -{% if platform.name == "ubuntu" %} - - sudo apt-get update -q - - sudo apt install -qy imagemagick - +{% if platform.name == "mac" %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - -# Platform specific UTR setup - - | -{% if platform.name == "win" %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat -{% else %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr -{% endif %} - -# Installing editor - - unity-downloader-cli -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} --fast --wait - -# Build Player - - | -{% if platform.name == "win" %} - utr.bat --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --testproject={{ project.path }} --scripting-backend={{ backend }} --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --artifacts-path=artifacts --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 -{% else %} - ./utr --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --testproject={{ project.path }} --scripting-backend={{ backend }} --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --artifacts-path=artifacts --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 -{% endif %} - + commands: + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} + - UnifiedTestRunner --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --testproject={{ project.path }} --scripting-backend={{ backend }} --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --artifacts-path=artifacts --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 artifacts: players: paths: @@ -54,18 +52,17 @@ desktop_standalone_build_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ logs: paths: - "artifacts/**/*" - dependencies: - .yamato/project-pack.yml#project_pack_-_{{ project.name }}_{{ platform.name }} {% endfor -%} {% endfor -%} {% endfor -%} {% endfor -%} - - - - -# Run phase + + + + +# RUN PHASE CONFIGURATION------------------------------------------------------------------------------------ {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} @@ -76,9 +73,12 @@ desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ type: {% if platform.name == "mac" %} {{ platform.type }} {% else %} {{ platform.type }}::GPU {% endif %} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.name == "mac" %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile). In this case it's specifically for macOS (if used for win/ubuntu then it will cause rust server to fail connecting) +{% endif %} -# Set additional variables for running the echo server -{% if platform.name != "win" %} +# Set additional variables for running the echo server (This is needed ONLY for NGOv2.X because relates to Distributed Authority) +{% if platform.name != "win" %} # Issues with win and mac are tracked in MTT-11606 variables: ECHO_SERVER_PORT: "7788" # Set this to ensure the DA codec tests will fail if they cannot connect to the echo-server @@ -87,19 +87,8 @@ desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ {% endif %} commands: -# Platform specific UTR setup - - | -{% if platform.name == "win" %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat -{% else %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr -{% endif %} - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - - unity-downloader-cli -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} --fast --wait - -# If ubuntu, run rust echo server -{% if platform.name != "win" %} +# If ubuntu, run rust echo server (This is needed ONLY for NGOv2.X because relates to Distributed Authority) +{% if platform.name != "win" %} # Issues with win and mac are tracked in MTT-11606 - git clone https://github.com/Unity-Technologies/mps-common-multiplayer-backend.git # Install rust - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y @@ -109,14 +98,8 @@ desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ - cd ./mps-common-multiplayer-backend/runtime && $HOME/.cargo/bin/cargo run --example ngo_echo_server -- --port $ECHO_SERVER_PORT & {% endif %} -# Run Standalone tests - - | -{% if platform.name == "win" %} - utr.bat --suite=playmode --player-load-path=build/players --artifacts-path=test-results --testproject={{ project.path }} --editor-location=.Editor --playergraphicsapi=Null --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 -{% else %} - ./utr --suite=playmode --player-load-path=build/players --artifacts-path=test-results --testproject={{ project.path }} --editor-location=.Editor --playergraphicsapi=Null --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 -{% endif %} - + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if backend == "il2cpp" %} -c il2cpp {% endif %} {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models + - UnifiedTestRunner --suite=playmode --player-load-path=build/players --artifacts-path=test-results --testproject={{ project.path }} --editor-location=.Editor --playergraphicsapi=Null --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=1800 artifacts: logs: paths: diff --git a/.yamato/mobile-standalone-test.yml b/.yamato/mobile-standalone-test.yml index 2ca2444b86..8169d34e8d 100644 --- a/.yamato/mobile-standalone-test.yml +++ b/.yamato/mobile-standalone-test.yml @@ -1,41 +1,56 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- + +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for Mobile platform test validation. + # Those tests cover both PlayMode and EditMode tests from package test assemblies. + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs are generated using nested loops through: + # 1. For all mobile platform (Android, iOS) + # 2. For all supported Unity Editor versions (for NGOv2.X this means 6000.0+ editors) + # 3. For the default project. -# Builds a player on mobile standalone platform and executes RuntimeTests (equivalent to PlayMode tests) of the NGO package in the Standalone build. -# Default project (testptoject) in this case is used as a context. -# Builds/Tests are made on each mobile platform (Android and iOS) as in project.metafile declaration -# Builds/Tests are made on each supported editor as in project.metafile declaration - -# For mobile devices it's necessary to split build and run phases -# For iOS we need to use il2cpp. For android we could use both but il2cpp is recommended so for now we will only use il2cpp as scripting backend -# iOS works only with ARM64 and Android is tested with both ARM64 and ARMv7 +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # For mobile devices a split is required into two phases: + # 1. Build Phase: Creates standalone players for mobile platforms + # 2. Run Phase: Executes runtime tests on actual mobile devices + # The Run phase uses build job as dependency + # Note: More of a Unity specific but test assemblies need to be included in the build phase command + # Note: All builds can be made on x64 machines since those are compatible with ARM64 target devices + +# PLATFORM SPECIFICS-------------------------------------------------------------------- + # iOS Requirements: + # Must use IL2CPP scripting backend + # Only supports ARM64 architecture + # Builds on macOS agents only -# Builds are made with x64 architecture machines since those are compatible to run on ARM64 devices + # Android Requirements: + # Uses IL2CPP scripting backend (recommended over Mono) + # Supports both ARM64 and ARMv7 architectures + # Can be build on any desktop platform + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # TODO: consider adding all projects that have tests + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + + +# BUILD PHASE CONFIGURATION------------------------------------------------------------------------------------ {% for project in projects.default -%} {% for platform in test_platforms.mobile_build -%} {% for editor in validation_editors.all -%} -build_{{ project.name }}_{{ platform.name }}_{{ editor }}: - name: Build {{ project.name }} - [{{ platform.name }}, {{ editor }}, il2cpp] +mobile_standalone_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: + name: Build {{ project.name }} - [{{ platform.name }}, {{ editor }}, il2cpp] agent: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} - commands: - # Installing tools - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - # Installing editor - - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp {% if platform.base == "mac" %} -c ios {% else %} -c android {% endif %} --fast --wait - - # Platform specific Build -{% if platform.base == "win" %} - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat - - utr.bat --suite=playmode --platform={{ platform.standalone }} --testproject={{ project.path }} --architecture={{ platform.architecture }} --scripting-backend=il2cpp --editor-location=.Editor --artifacts-path=artifacts --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 -{% else %} - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr - - chmod +x ./utr - - ./utr --suite=playmode --platform={{ platform.standalone }} --testproject={{ project.path }} --architecture={{ platform.architecture }} --scripting-backend=il2cpp --editor-location=.Editor --artifacts-path=artifacts --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} + commands: + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor -c il2cpp {% if platform.base == "mac" %} -c ios {% else %} -c android {% endif %} + - UnifiedTestRunner --suite=playmode --platform={{ platform.standalone }} --testproject={{ project.path }} --architecture={{ platform.architecture }} --scripting-backend=il2cpp --editor-location=.Editor --artifacts-path=artifacts --testfilter="Unity.Netcode.RuntimeTests.*" --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 artifacts: players: paths: @@ -43,40 +58,30 @@ build_{{ project.name }}_{{ platform.name }}_{{ editor }}: logs: paths: - "artifacts/**/*" - - variables: - CI: true - ENABLE_BURST_COMPILATION: False {% endfor -%} {% endfor -%} {% endfor -%} -# Executes RuntimeTests of the NGO package in the Standalone build for mobiles +# RUN PHASE CONFIGURATION------------------------------------------------------------------------------------ {% for project in projects.default -%} {% for platform in test_platforms.mobile_test -%} {% for editor in validation_editors.all -%} -run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }}: +mobile_standalone_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: name: Run {{ project.name }} Tests - [{{ platform.name }}, {{ editor }}, il2cpp] agent: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} {% if platform.model %} - model: {{ platform.model }} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} - # Skip repository cloning - skip_checkout: true commands: - # Installing tools - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - # Installing editor. We still need the editor to run tests on standalone build - - unity-downloader-cli -u {{ editor }} -c Editor -c il2cpp {% if platform.base == "mac" %} -c ios {% else %} -c android {% endif %} --fast --wait + # Installing editor. We still need the editor to run tests on standalone build and for that the Editor is required + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor -c il2cpp {% if platform.base == "mac" %} -c ios {% else %} -c android {% endif %} {% if platform.standalone == "Android" %} # Download standalone UnityTestRunner and ADB setup - - command: curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat - command: wget http://artifactory-slo.bf.unity3d.com/artifactory/mobile-generic/android/ADBKeys.zip!/adbkey.pub -O %USERPROFILE%/.android/adbkey.pub - command: wget http://artifactory-slo.bf.unity3d.com/artifactory/mobile-generic/android/ADBKeys.zip!/adbkey -O %USERPROFILE%/.android/adbkey - command: gsudo NetSh Advfirewall set allprofiles state off @@ -84,23 +89,21 @@ run_{{ project.name }}_tests_{{ platform.name }}_{{ editor }}: # Connect to Android device - command: '"%ANDROID_SDK_ROOT%\platform-tools\adb.exe" connect %BOKKEN_DEVICE_IP%' - # Run tests + # Run tests for Android devices - | set ANDROID_DEVICE_CONNECTION=%BOKKEN_DEVICE_IP% - utr --suite=playmode --platform={{ platform.standalone }} --artifacts-path=test-results --player-load-path=build/players --testproject={{ project.path }} --editor-location=.Editor --player-connection-ip=%BOKKEN_HOST_IP% --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=3600 + UnifiedTestRunner --suite=playmode --platform={{ platform.standalone }} --artifacts-path=test-results --player-load-path=build/players --testproject={{ project.path }} --editor-location=.Editor --player-connection-ip=%BOKKEN_HOST_IP% --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=3600 {% else %} - # Download standalone UnityTestRunner - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr - # Run tests - - ./utr --suite=playmode --platform={{ platform.standalone }} --artifacts-path=test-results --player-load-path=build/players --testproject={{ project.path }} --editor-location=.Editor --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=3600 + # Run tests for non-Android devices + - UnifiedTestRunner --suite=playmode --platform={{ platform.standalone }} --artifacts-path=test-results --player-load-path=build/players --testproject={{ project.path }} --editor-location=.Editor --fail-on-assert --reruncount=1 --clean-library-on-rerun --timeout=3600 {% endif %} artifacts: logs: paths: - "test-results/**/*" dependencies: - - .yamato/mobile-standalone-test.yml#build_{{ project.name }}_{{ platform.name }}_{{ editor }} + - .yamato/mobile-standalone-test.yml#mobile_standalone_build_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} {% endfor -%} diff --git a/.yamato/package-pack.yml b/.yamato/package-pack.yml index 492c08ef78..f4b02dc19a 100644 --- a/.yamato/package-pack.yml +++ b/.yamato/package-pack.yml @@ -1,9 +1,28 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Packs Netcode for GameObjects together with performing initial checks. -# For this job no specific platform support and no running Unity instance is required so small agent (as per project.metafile definition) could be used to save resources and speed up the process. -# If everyone adheres to this rule it can create bottlenecks (since everyone would use this machine) so we decided to pack with the same platform as the given job runs on +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for packing a specific package. It generates package artifacts (.tgz) required for testing and publishing, ensuring all dependencies are properly bundled and validated before any test execution. + # The job itself doesn't test anything specific but rather it prepares project packages that will be consumed by other pipeline jobs. + # The job performs additional validation by using Package Verification Pipeline (PVP). It includes x-ray validation for quick package structure verification + # Because of that validation we can detect errors at the early stage of testing so not to waste CI resources + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs are generated using nested loops through: + # 1. For all desktop platforms (Windows, Ubuntu, macOS) + +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # Job does not require Unity Editor in order to perform packing. + # Job uses PVP x-ray for lightweight validation + # Job generates both packages artifacts and pvp-results file. + # In theory, we could just use one platform for packing projects (for example ubuntu) but in order to reduce confusion we are using same platform as the job utilizing project pack as dependency. + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + # TODO: we should aim to replace target PVP profile from supported to gold + +#------------------------------------------------------------------------------------ + {% for platform in test_platforms.desktop -%} package_pack_-_ngo_{{ platform.name }}: name: Package Pack (and x-ray) - NGO [{{ platform.name }}] @@ -11,6 +30,9 @@ package_pack_-_ngo_{{ platform.name }}: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} timeout: 0.25 variables: XRAY_PROFILE: "supported ./pvpExceptions.json" @@ -25,28 +47,4 @@ package_pack_-_ngo_{{ platform.name }}: packages: paths: - "upm-ci~/**" -{% endfor -%} - - -# This is in essence the same job as the one above with the difference that upm-ci is used instead of upm-pvp -# The reason for using it is that I had some problems with Code Coverage which in its current form uses upm-ci but if we would use the other pack job (the one above) we would need to use upm-pvp -# I had some problems with getting it to work so as temporary solution I created this pack job which is used ONLY as a dependency of Code Coverage job (other jobs use the above definition of pack job) -# TODO: remove this job and utilize the above one for Code Coverage job. This is tracked in MTT-11383 -{% for platform in test_platforms.default -%} -{% for project in projects.default -%} -package_pack_-_ngo_{{ platform.name }}_upmCI: - name: Package Pack (legacy upm-ci) - NGO [{{ platform.name }}] - agent: - type: {{ platform.type }} - image: {{ platform.image }} - flavor: {{ platform.flavor }} - timeout: 0.25 - commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - upm-ci project pack --project-path {{ project.path }} - artifacts: - packages: - paths: - - "upm-ci~/packages/**/*" -{% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/package-tests.yml b/.yamato/package-tests.yml index 79815ebc16..2d304ad02e 100644 --- a/.yamato/package-tests.yml +++ b/.yamato/package-tests.yml @@ -1,9 +1,29 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Executes PlayMode and EditMode tests of the NGO package in the Editor context -# Those tests run in the editor so we don't need to consider different scripting backends or architectures -# Tests are executed for all supported editors on each desktop platform as in project.metafile declaration +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for execution of package-specific tests in Unity Editor context + # Those tests cover both PlayMode and EditMode tests from package test assemblies + # Additionally it combines Package Verification Pipeline (PVP) validation. This ensures that package is compatible with Unity standards + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs are generated using nested loops through: + # 1. For all desktop platforms (Windows, Ubuntu, macOS) + # 2. For all supported Unity Editor versions (for NGOv2.X this means 6000.0+ editors) + +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # This job runs in Editor context only (no player builds required) + # No scripting backend variations needed (Editor context) + # Architecture variations not applicable (Editor context) + # Requires project packaging as prerequisite (dependency job) + # Uses PVP for package validation. Specifically it looks for supported profiles which we should conform to but takes ./pvpExceptions.json file into consideration where we note our known issues related to PVP checks + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + # TODO: we should aim to replace target PVP profile from supported to gold + +#------------------------------------------------------------------------------------ + {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} package_test_-_ngo_{{ editor }}_{{ platform.name }}: @@ -12,25 +32,22 @@ package_test_-_ngo_{{ editor }}_{{ platform.name }}: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} variables: XRAY_PROFILE: "supported ./pvpExceptions.json" UNITY_EXT_LOGGING: 1 commands: - - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait - # Platform specific UTR setup - - | -{% if platform.name == "win" %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat -{% else %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr -{% endif %} + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models. - # Validate packages. + # Validate PVP checks for package. - upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects" --results pvp-results - upm-pvp require {% if platform.name == "win" %}"%XRAY_PROFILE%"{% else %}"$XRAY_PROFILE"{% endif %} --results pvp-results + # Run UTR to test packages. - upm-pvp create-test-project test-project --packages "upm-ci~/packages/*.tgz" --filter "com.unity.netcode.gameobjects" --unity .Editor - - {% if platform.name == "ubuntu" %}DISPLAY=:0 {% endif %} {% if platform.name == "win" %} utr.bat {% else %} ./utr {% endif %} --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --artifacts-path=test-results "--ff={ops.upmpvpevidence.enable=true}" --extra-editor-arg=-testCategory --extra-editor-arg=!Performance --reruncount=1 --clean-library-on-rerun + - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --artifacts-path=test-results "--ff={ops.upmpvpevidence.enable=true}" --reruncount=1 --clean-library-on-rerun artifacts: logs: paths: diff --git a/.yamato/performance-tests.yml b/.yamato/performance-tests.yml index abdbef444e..26bf8102f4 100644 --- a/.yamato/performance-tests.yml +++ b/.yamato/performance-tests.yml @@ -1,8 +1,27 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Performance tests for the `com.unity.netcode.gameobjects` package. No performance data will be reported. -# Performance tests are executed within editor on desktop platforms in context of default project (testproject) +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for executing performance tests for NGO package. + # Its configuration is set to not report any data but just to give results (at least in current state since we don't have any tests to run). + # Currently because of lack of performance tests this job will always return "no tests have been selected" and because oif that it's not included in any trigger jobs. + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs configurations are generated using nested loops through: + # 1. For all desktop platforms (Windows, Ubuntu, macOS) + # 2. For all supported Unity Editor versions (For NGOv2.X it means 6000+) + # 3. For the default project (project is used only as a context for the build). TODO-comment: if performance tests would be included in projects then we should make an approperiate split. + +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # Tests are run in Editor context only + # No performance metrics are reported to monitoring systems + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # TODO: Currently NGO don't have any performance tests so this job is a placeholder for the future. We should discuss how to approach the topic of performance testing + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + +#------------------------------------------------------------------------------------ + {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} {% for project in projects.default -%} @@ -12,44 +31,17 @@ performance_editor_tests_-_NGO_{{ platform.name }}_{{ editor }}_no_data_reportin type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} - commands: - # Installing tools -{% if platform.name == "ubuntu" %} - - sudo apt-get update -q - - sudo apt install -qy imagemagick -{% endif %} - - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - # Platform specific UTR setup -{% if platform.name == "win" %} - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat -{% else %} - - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} - - # Installing editor - - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait - - # Build Player - - | -{% if platform.name == "win" %} - utr.bat --suite=editor --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --timeout=3600 --artifacts-path=artifacts --extra-editor-arg=-assemblyNames --extra-editor-arg=Unity.NetCode.* --extra-editor-arg=-testCategory --extra-editor-arg=Performance --extra-editor-arg=-enablePackageManagerTraces --reruncount=1 --clean-library-on-rerun --dontreportperformancedata -{% else %} - ./utr --suite=editor --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --timeout=3600 --artifacts-path=artifacts --extra-editor-arg=-assemblyNames --extra-editor-arg=Unity.NetCode.* --extra-editor-arg=-testCategory --extra-editor-arg=Performance --extra-editor-arg=-enablePackageManagerTraces --reruncount=1 --clean-library-on-rerun --dontreportperformancedata -{% endif %} - + commands: + - unity-downloader-cli -u {{ editor }} -c Editor --wait {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models. Installing basic editor + - UnifiedTestRunner --suite=editor --suite=playmode --testproject={{ project.path }} --editor-location=.Editor --timeout=3600 --artifacts-path=artifacts --extra-editor-arg=-assemblyNames --extra-editor-arg=Unity.NetCode.* --extra-editor-arg=-testCategory --extra-editor-arg=Performance --extra-editor-arg=-enablePackageManagerTraces --reruncount=1 --clean-library-on-rerun --dontreportperformancedata + # TODO: when performance tests will be present we need to add actuall execution of this test artifacts: logs: paths: - - '*.log' - - '*.xml' - - artifacts/**/* - - {{ project.name }}/Logs/** - - {{ project.name }}/Library/*.log - - {{ project.name }}/*.log - - {{ project.name }}/Builds/*.log - - build/test-results/** + - "artifacts/**/*" {% endfor -%} {% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/project-pack.yml b/.yamato/project-pack.yml index 0963d99271..5a0337a6a1 100644 --- a/.yamato/project-pack.yml +++ b/.yamato/project-pack.yml @@ -1,9 +1,26 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Jobs that iterate through and packs all NGO projects listed (to use in different jobs) -# For this job no specific platform support and no running Unity instance is required so small agent (as per project.metafile definition) coul be used to save resources and speed up the process -# If everyone adheres to this rule it can create bottlenecks (since everyone would use this machine) so we decided to pack project with the same platform as the given job runs on +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for packing a specific project. It generates package artifacts (.tgz) required for testing and publishing, ensuring all dependencies are properly bundled and validated before any test execution. + # The job itself doesn't test anything specific but rather it prepares project packages that will be consumed by other pipeline jobs. + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs configurations are generated using nested loops through: + # 1. For all projects (testproject, minimalproject, testproject-tools-integration). + # 2. For all desktop platforms (Win, Ubuntu, Mac) + +# TECHNICAL CONSIDERATIONS-------------------------------------------------------------------- + # Job does not require Unity Editor in order to perform packing. + # In theory, we could just use one platform for packing projects (for example ubuntu) but in order to reduce confusion we are using same platform as the job utilizing project pack as dependency. + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + # TODO: Currently upm-ci is being used but in the future it will be replaced by upm-pvp. Additionally this would allow us to run PVP checks on projects + + +#-------------------------------------------------------------------------------------- + {% for project in projects.all -%} {% for platform in test_platforms.desktop -%} project_pack_-_{{ project.name }}_{{ platform.name }}: @@ -12,8 +29,11 @@ project_pack_-_{{ project.name }}_{{ platform.name }}: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} commands: - - npm install -g upm-ci-utils@stable --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm # upm-ci is not preinstalled on the image so we need to download it - upm-ci project pack --project-path {{ project.path }} artifacts: packages: diff --git a/.yamato/project-standards.yml b/.yamato/project-standards.yml index b0a74ad6a6..6a70daf149 100644 --- a/.yamato/project-standards.yml +++ b/.yamato/project-standards.yml @@ -1,9 +1,33 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Project standards are being checked for package (in project context). -# It should be enough to perform the test on default project (testproject) but to make sure that all projects conform to our standards job for each of them will be available. -# Tests are executed for default editor (trunk) on default platform (ubuntu) as in project.metafile declaration since results would be the same across editors and platforms. +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for validating project compliance with NGO coding standards and conventions + # Standards validation includes: + # Code formatting compliance + # Project structure validation + # Coding convention adherence + # Solution file synchronization + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs configurations are generated using nested loops through: + # 1. For all NGO projects (testproject, testproject-tools-interation, minimalproject) + # 2. For default platform only (Ubuntu) since standards would not vary between platforms (no need for checks on more platforms) + # 3. For default editor version (trunk) since standards would not vary between editors (no need for checks on more editors) + +# TECHNICAL CONSTRAINTS--------------------------------------------------------------- + # Requires .NET SDK installed (should be preinstalled on the image so we just check for version) + # Needs Unity Editor for solution synchronization + # Uses custom standards validation tool (netcode.standards) + # Generates no test artifacts (pass/fail only). Eventual failure will be visible in the logs + +# QUALITY THOUGHTS-------------------------------------------------------------------- + # While testproject validation would be sufficient, since it validates both project and package (where package is our main concern) jobs for all projects are being generated to ensure that we conform to quality standards in all projects. + # TODO: consider modifying the approach and adding checks for minimal supported editor. In case of NGOv1.X it has proven to yield different results (But since NGOv2.X support starts from 6000.0 editor it's not that time pressuring) + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + +#------------------------------------------------------------------------------------ + {% for project in projects.all -%} {% for platform in test_platforms.default -%} {% for editor in validation_editors.default -%} @@ -13,18 +37,17 @@ standards_{{ platform.name }}_{{ project.name }}_{{ editor }}: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} commands: - # .NET environment setup + # .NET environment setup. Ensures required .NET SDK and formatting tools are available - dotnet --version - dotnet format --version - # Unity setup - - pip install unity-downloader-cli --upgrade --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - unity-downloader-cli -u {{ editor }} -c editor --wait --fast - - .Editor/Unity -batchmode -nographics -logFile - -executeMethod Packages.Rider.Editor.RiderScriptEditor.SyncSolution -projectPath {{ project.path }} -quit - - # Standards check - - dotnet run --project=dotnet-tools/netcode.standards -- --project={{ project.path }} --check + - unity-downloader-cli --fast --wait -u {{ editor }} -c editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models. Downloads basic editor + - .Editor/Unity -batchmode -nographics -logFile - -executeMethod Packages.Rider.Editor.RiderScriptEditor.SyncSolution -projectPath {{ project.path }} -quit # This command is used to invoke Unity in a "headless" mode. It's used to sync the project + - dotnet run --project=dotnet-tools/netcode.standards -- --project={{ project.path }} --check # Runs standards check {% endfor -%} {% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/project-tests.yml b/.yamato/project-tests.yml index ec91e04540..a39f957480 100644 --- a/.yamato/project-tests.yml +++ b/.yamato/project-tests.yml @@ -1,10 +1,29 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Executes PlayMode and EditMode tests of the given project (ones that have tests) in the Editor context -# Those tests don't include NGO package tests since they are handled on their respective jobs. -# Those tests run in the editor so we don't need to consider different scripting backends or architectures -# Tests are executed for all supported editors on each desktop platform as in project.metafile declaration +# DESCRIPTION-------------------------------------------------------------------------- + # This job executes project-specific tests in Unity Editor context + # Those tests cover both PlayMode and EditMode tests from project test assemblies + # NGO package tests are NOT being executed within this job (those are handled in separate package test jobs) + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs configurations are generated using nested loops through: + # 1. For all projects WITH TESTS (filtered by has_tests flag) (testproject, testproject-tools-interation) [For more info look into project.metafile configuration] + # 2. For all desktop platforms (Windows, Ubuntu, macOS) + # 3. For all supported Unity Editor versions (for NGOv2.X this means 6000.0+ editors) + +# TECHNICAL CONSIDERATIONS--------------------------------------------------------------- + # This job runs in Editor context only (no player builds is required) + # No scripting backend variations needed (Editor context) + # Architecture variations not applicable (Editor context) + # Requires project packaging as prerequisite (dependency job) + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # TODO: Currently upm-ci is being used but in the future it will be replaced by upm-pvp + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + +#------------------------------------------------------------------------------------ + {% for project in projects.all -%} {% if project.has_tests == "true" -%} {% for platform in test_platforms.desktop -%} @@ -15,10 +34,13 @@ test_{{ project.name }}_{{ platform.name }}_{{ editor }}: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm - - unity-downloader-cli -u {{ editor }} -c Editor --fast --wait - - {% if platform.name == "ubuntu" %}DISPLAY=:0 && {% endif %}upm-ci project test -u {{ editor }} --project-path {{ project.path }} --type project-tests --extra-utr-arg="--extra-editor-arg=-testCategory --extra-editor-arg=!Performance --reruncount=1 --clean-library-on-rerun" + - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm # upm-ci is not preinstalled on the image so we need to download it + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models. Installing basic editor for tests execution + - upm-ci project test -u {{ editor }} --project-path {{ project.path }} --type project-tests --extra-utr-arg="--reruncount=1 --clean-library-on-rerun" # project tests execution via upm-ci artifacts: logs: paths: diff --git a/.yamato/project-updated-dependencies-test.yml b/.yamato/project-updated-dependencies-test.yml index 513087f3c4..19ae953e27 100644 --- a/.yamato/project-updated-dependencies-test.yml +++ b/.yamato/project-updated-dependencies-test.yml @@ -1,27 +1,48 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Executes package tests for each package referenced in the project using the newest compatible version of the package dependencies. -# This is just to ensure that no critical change is made. It will be run on a default editor and default platform (it can be discussed if we should run it per all editors and platforms) -{% for project in projects.default -%} -{% for platform in test_platforms.default -%} -{% for editor in validation_editors.default -%} -updated-dependencies_{{ project.name }}_NGO_{{ platform.name }}_{{ editor }}: +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible fo validating package compatibility with latest dependency versions. + # This helps detect potential breaking changes from dependency updates early + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs configurations are generated using nested loops through: + # 1. For all projects (testproject, minimalproject, testproject-tools-integration). + # 2. For all desktop platforms (Win, Ubuntu, Mac) + # 3. For all supported editors (For NGOv2.X it means 6000+) + +# TECHNICAL CONSIDERATIONS---------------------------------------------------------------- + # This job requires successful project packaging before execution (job dependency) + # This job tests only NGO package dependencies (com.unity.netcode.gameobjects) + # The results are being generated in upm-ci~/test-results directory (specific of upm-ci) + +# QUALITY CONSIDERATIONS--------------------------------------------------------------------- + # TODO: Currently upm-ci is being used but in the future it will be replaced by upm-pvp + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + +#-------------------------------------------------------------------------------------- + +{% for project in projects.all -%} +{% for platform in test_platforms.desktop -%} +{% for editor in validation_editors.all -%} +updated-dependencies_{{ project.name }}_NGO_{{ platform.name }}_{{ editor }}: name : Updated Dependencies Test - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}] agent: type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) +{% endif %} commands: - - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm + - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm # upm-ci is not preinstalled on the image so we need to download it - upm-ci project test -u {{ editor }} --project-path {{ project.path }} --type updated-dependencies-tests --package-filter com.unity.netcode.gameobjects artifacts: logs: paths: - "upm-ci~/test-results/**/*" dependencies: - - path: .yamato/project-pack.yml#project_pack_-_{{ project.name }}_{{ platform.name }} - rerun: always + - .yamato/project-pack.yml#project_pack_-_{{ project.name }}_{{ platform.name }} {% endfor -%} {% endfor -%} {% endfor -%} diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 43c1202c90..a04afbc533 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -1,4 +1,22 @@ -# The small agent was created to handle jobs that don't involve running Unity (for example pack job). +# DESCRIPTION----------------------------------------------------------------------------------------------------------------- +# This file is the central configuration file defining test environments, platforms, projects, editors and other parameters used. +# Data from this file is used to generate CI pipeline configurations through templating in other .yamato/ files. +# This provides consistent environment definitions across all CI jobs. It's easier to modify values in central place (for example image reference) than having to modify every job + +# VALUE MEANING---------------------------------------------------------------------------------------------------------------------------- +# Usual fields being defined are + # name --> Identifier used to distinguish different configurations in the CI pipeline + # type --> Specifies the Bokken agent type (e.g., Unity::VM, Unity::VM::osx, Unity::mobile::shield) + # image --> Defines the package-ci Bokken image to use for the environment (e.g., package-ci/ubuntu-22.04:v4). This is basically a device configuration + # flavor --> Determines the VM size/resources (e.g., b1.small, b1.large, m1.mac) + # standalone --> Specifies the build target platform (e.g., StandaloneLinux64, Android, IOS) + # model --> Defines specific hardware model requirements (e.g., rtx2080, SE for iPhone SE) + # base --> Indicates the base operating system for build operations (e.g., win, mac) + # architecture --> Specifies the target CPU architecture (e.g., armv7, arm64) + + +# SMALL AGENT CONFIGURATION--------------------------------------------------------------------------------------------------- +# The small agent was created to handle jobs that don't involve running Unity and are in general super light when it comes to resource usage (for example pack job). # It uses ubuntu since Linux VMs are faster and cheaper to provision than Mac or Windows Virtual Machines (VMs). # The b1.small flavour is nearly always sufficient for jobs that don’t involve running Unity Editor. small_agent_platform: @@ -7,7 +25,13 @@ small_agent_platform: image: package-ci/ubuntu-22.04:v4 flavor: b1.small -# Platforms that project will/can be tested on. + +# PLATFORM CONFIGURATIONS---------------------------------------------------------------------------------------------------------------- +# test_platforms specifies platforms that project will/can be tested on. +# For desktops we include Windows, Ubuntu and Mac, for mobiles we include Android and iOS, for consoles we include ps4, ps5, switch, xbox360 and xboxOne +# For mobile and console a split was required for build and test jobs and this is also applied for desktops for consistency (though for desktop this split is not required) + + test_platforms: # Default platform is used for all basic jobs. Ubuntu was chosen since it's fastest and most available default: @@ -31,10 +55,12 @@ test_platforms: model: rtx2080 - name: mac type: Unity::VM::osx - image: package-ci/macos-13:v4 + image: package-ci/macos-13-arm64:v4 # ARM64 to support M1 model (below) flavor: m1.mac standalone: StandaloneOSX + model: M1 # The default model (an x64 Intel Mac VM) quite often caused a known issue of doing all the bitflips in packages resulting in failures # For mobile devices there is a split between the build and run phase so there is a need of splitting specification for both + # TODO: For android we could consider adding ARM64 configuration mobile_build: - name: android type: Unity::VM @@ -43,7 +69,7 @@ test_platforms: standalone: Android base: win architecture: armv7 - # iOS modern builds are ARM64-only, thus no testing with armv7 (as in android case) + # iOS modern builds are ARM64-only, thus no testing with armv7 - name: ios-arm64 type: Unity::VM::osx image: package-ci/macos-13:v4 @@ -121,9 +147,10 @@ test_platforms: flavor: b1.large standalone: GameCoreScarlett - -# Editors to be used for testing. -# Since NGOv2 official support started from U6 it means that only those editors should be used for testing +# EDITOR CONFIGURATIONS------------------------------------------------------------------------------- +# Editors to be used for testing. NGOv2.X official support started from 6000.0 editor +# TODO: When a new editor will be released it should be added to this list + validation_editors: default: - trunk @@ -133,15 +160,18 @@ validation_editors: - trunk -# Scripting backends used by Standalone RunTimeTests +# Scripting backends used by Standalone RunTimeTests--------------------------------------------------- + scripting_backends: - mono - il2cpp -# Projects within the repository that will be tested. Name will be used -# for job ids, so it should not contain spaces/non-supported characters -# The default project will also be used for validation, code coverage etc. +# PROJECTS CONFIGURATIONS------------------------------------------------------------------------------- +# Projects within the repository that will be tested/build. +# The default project should be used for general validation, code coverage and other tests where choice of project should not matter (because we are interested in NGO package) +# has_tests --> describes if projects contains any tests to run. + projects: default: - name: testproject diff --git a/.yamato/webgl-build.yml b/.yamato/webgl-build.yml index 15db4b16ec..393998f407 100644 --- a/.yamato/webgl-build.yml +++ b/.yamato/webgl-build.yml @@ -1,53 +1,55 @@ -{% metadata_file .yamato/project.metafile %} +{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file. --- -# Builds a player on WebGl standalone platform without executing any tests. -# This setup performs build-only validation since WebGL runs in browser and for tests to be executed we would need to -# consider having a web server, browser automation and overall complex test setup. -# Default project (testptoject) in this case is used as a context. -# WebGL requires Il2cpp scripting backend so mono is not considered -# ARM64 architectures are not considered since Windows_arm64 is recommended to use only after builds and when it comes to macOS_arm64 there is problem with OpenCL not being available -# Builds are made on each desktop platform as in project.metafile declaration +# DESCRIPTION-------------------------------------------------------------------------- + # This job is responsible for validating a successful building of a player on WebGl standalone platform. + # This job WILL NOT execute any tests, we only validate the building process. + # This is because WebGL runs in browser and for tests to be executed we would need to consider having a web server, browser automation and overall complex test setup which currently we don't have. + +# CONFIGURATION STRUCTURE-------------------------------------------------------------- + # Jobs configurations are generated using nested loops through: + # 1. For the default project (project is used only as a context for the build). + # 2. For all desktop platforms (Windows, Ubuntu, macOS) + # 3. For all supported Unity Editor versions (For NGOv2.X it means 6000+) + +# TECHNICAL CONSIDERATIONS---------------------------------------------------------------- + # WebGL requires IL2CPP scripting backend (Mono is not supported) + # We are not using ARM64 architectures since we only perform a build action. x64 architectures are preferred for build phase (in order to optimize available resource usage) + # We only perform build validation (no runtime testing) + +# QUALITY CONSIDERATIONS-------------------------------------------------------------------- + # In the future we could try to implement an infrastructure to run test in webgl context but this could be quite complicated and would need to be evaluated if it's worth it + # To see where this job is included (in trigger job definitions) look into _triggers.yml file + +#-------------------------------------------------------------------------------------- + {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} -webgl_build_{{ project.name }}_{{ platform }}_{{ editor }}: +webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: name: WebGl Build - {{ project.name }} [{{ platform.name }}, {{ editor }}, il2cpp] agent: - type: {% if platform.name == "mac" %} {{ platform.type }} {% else %} {{ platform.type }}::GPU {% endif %} + type: {{ platform.type }} image: {{ platform.image }} flavor: {{ platform.flavor }} - commands: - # Installing tools - - pip install unity-downloader-cli --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple - - # Platform specific UTR setup - - | -{% if platform.name == "win" %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr.bat --output utr.bat -{% else %} - curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr && chmod +x utr +{% if platform.model %} + model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} + commands: + - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor -c webgl -c il2cpp {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models. Downloading the editor with additional webgl and il2cpp components - # Installing editor - - unity-downloader-cli -u {{ editor }} -c Editor -c webgl -c il2cpp -w --fast - - # Build Player - - | -{% if platform.name == "win" %} - utr.bat --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts_path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg="-cloudEnvironment staging" --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 -{% else %} - ./utr --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts-path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg="-cloudEnvironment staging" --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 -{% endif %} - + # The following step builds the player with defined options such as: + # Suite parameter if defined since it's a mandatory field to define which test suite should be used, but it doesn't matter in this case since we won't run any tests (--suite) + # Editor is run in batchmode, which means that Unity runs command line arguments without the need for human interaction. It also suppresses pop-up windows that require human interaction (such as the Save Scene window). We should always run Unity in batch mode when using command line arguments, because it allows automation to run without interruption. (--extra-editor-arg=-batchmode) + # Engine is initialized in ‘nographics’ mode since we don't need any graphics for this case (--extra-editor-arg=-nographics) + # In case of failure the job will be rerunned once (--reruncount=1) with clean library (--clean-library-on-rerun) + # This will perform only building phase (--build-only) with a timeout of 3m (--timeout=1800) + - UnifiedTestRunner --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts_path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 artifacts: logs: paths: - "artifacts/**/*" - "build/players/**/*" - variables: - CI: true - ENABLE_BURST_COMPILATION: False {% endfor -%} {% endfor -%} {% endfor -%} From cab4d78a70fd5aefb9c3471f39c789e0518a4e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:36:14 +0100 Subject: [PATCH 205/236] docs: [2.X] fixes of PVP exceptions (#3225) This PR focuses on fixing PVP errors related to PVP-151-1 "**_Public APIs should be documented_**" and all remaining PVP errors **present in pvpExceptions.json file** We currently have 768 errors to address so this PR will be a collaborative effort. If anyone want's to add to it what we should do is: 1. Check errors present in pvpExceptions.json file (preferably under "PVP-150-1", choose some to fix 2. Address those errors and when making commit remove the fixed errors from pvpExceptions file In general [new CI implementation](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3193) will catch all new errors like this so all we need to do is fix the present ones. This PR **does not** focus on PVP present in gold profile but if capacity allows, those can also be addressed. **Error present in "PVP-41-1" is to be expected** so there is no need of fixing this one! --------- Co-authored-by: Noel Stephens Co-authored-by: Emma --- .../Components/AnticipatedNetworkTransform.cs | 14 +++ .../Messaging/RpcTargets/BaseRpcTarget.cs | 6 +- .../Collections/NetworkList.cs | 93 ++++++++++++++----- 3 files changed, 88 insertions(+), 25 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index d99bd4dc2f..913447beb1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -50,10 +50,24 @@ public class AnticipatedNetworkTransform : NetworkTransform internal override bool HideInterpolateValue => true; #endif + /// + /// Represents a complete transform state for network synchronization and anticipation + /// public struct TransformState { + /// + /// The position component of the transform state in world space coordinates. + /// public Vector3 Position; + + /// + /// The rotation component of the transform state as a quaternion. + /// public Quaternion Rotation; + + /// + /// The scale component of the transform state in local space. + /// public Vector3 Scale; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs index 9d81bdecf9..66c88b3eec 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -29,9 +29,9 @@ internal BaseRpcTarget(NetworkManager manager) } /// - /// Can be used to provide additional lock checks before disposing the target. + /// Verifies the target can be disposed based on its lock state. /// - /// The exception thrown if the target is still locked when disposed. + /// Thrown when attempting to dispose a locked temporary RPC target protected void CheckLockBeforeDispose() { if (m_Locked) @@ -41,7 +41,7 @@ protected void CheckLockBeforeDispose() } /// - /// Invoked when the target is disposed. + /// Releases resources used by the RPC target. /// public abstract void Dispose(); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 53b7f4f8fb..72258c77f5 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -21,12 +21,12 @@ public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatabl public delegate void OnListChangedDelegate(NetworkListEvent changeEvent); /// - /// The callback to be invoked when the list gets changed + /// Creates A NetworkList/> /// public event OnListChangedDelegate OnListChanged; /// - /// Constructor method for + /// Constructor method for /// public NetworkList() { } @@ -132,7 +132,7 @@ public override void WriteDelta(FastBufferWriter writer) } } - /// + /// public override void WriteField(FastBufferWriter writer) { writer.WriteValueSafe((ushort)m_List.Length); @@ -158,12 +158,12 @@ public override void ReadField(FastBufferReader reader) /// public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { - /// This is only invoked by and the only time - /// keepDirtyDelta is set is when it is the server processing. To be able to handle previous - /// versions, we use IsServer to keep the dirty states received and the keepDirtyDelta to - /// actually mark this as dirty and add it to the list of s to - /// be updated. With the forwarding of deltas being handled by , - /// once all clients have been forwarded the dirty events, we clear them by invoking . + // This is only invoked by and the only time + // keepDirtyDelta is set is when it is the server processing. To be able to handle previous + // versions, we use IsServer to keep the dirty states received and the keepDirtyDelta to + // actually mark this as dirty and add it to the list of s to + // be updated. With the forwarding of deltas being handled by , + // once all clients have been forwarded the dirty events, we clear them by invoking . var isServer = m_NetworkManager.IsServer; reader.ReadValueSafe(out ushort deltaCount); for (int i = 0; i < deltaCount; i++) @@ -406,13 +406,22 @@ internal override void PostDeltaRead() } } - /// + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator for the . public IEnumerator GetEnumerator() { return m_List.GetEnumerator(); } - /// + /// + /// Adds an item to the end of the . + /// + /// The item to be added to the list. + /// + /// This method checks for write permissions before adding the item. + /// public void Add(T item) { // check write permissions @@ -434,7 +443,12 @@ public void Add(T item) HandleAddListEvent(listEvent); } - /// + /// + /// Removes all items from the . + /// + /// + /// This method checks for write permissions before clearing the list. + /// public void Clear() { // check write permissions @@ -454,14 +468,25 @@ public void Clear() HandleAddListEvent(listEvent); } - /// + /// + /// Determines whether the contains a specific value. + /// + /// The object to locate in the . + /// if the is found in the ; otherwise, . public bool Contains(T item) { int index = m_List.IndexOf(item); return index != -1; } - /// + /// + /// Removes the first occurrence of a specific object from the NetworkList. + /// + /// + /// This method checks for write permissions before removing the item. + /// + /// The object to remove from the list. + /// if the item was successfully removed from the list; otherwise, . public bool Remove(T item) { // check write permissions @@ -488,16 +513,29 @@ public bool Remove(T item) return true; } - /// + /// + /// Gets the number of elements contained in the . + /// public int Count => m_List.Length; - /// + /// + /// Determines the index of a specific in the . + /// + /// The object to remove from the list. + /// The index of the if found in the list; otherwise, -1. public int IndexOf(T item) { return m_List.IndexOf(item); } - /// + /// + /// Inserts to the at the specified . + /// + /// + /// This method checks for write permissions before inserting the item. + /// + /// The index at which the item should be inserted. + /// The item to insert. public void Insert(int index, T item) { // check write permissions @@ -527,7 +565,13 @@ public void Insert(int index, T item) HandleAddListEvent(listEvent); } - /// + /// + /// Removes the item at the specified . + /// + /// + /// This method checks for write permissions before removing the item. + /// + /// The index of the element to remove. public void RemoveAt(int index) { // check write permissions @@ -549,9 +593,14 @@ public void RemoveAt(int index) HandleAddListEvent(listEvent); } - - - /// + /// + /// Gets or sets the element at the specified index in the . + /// + /// + /// This method checks for write permissions before setting the value. + /// + /// The zero-based index of the element to get or set. + /// The element at the specified index. public T this[int index] { get => m_List[index]; @@ -587,7 +636,7 @@ private void HandleAddListEvent(NetworkListEvent listEvent) } /// - /// This is actually unused left-over from a previous interface + /// This method should not be used. It is left over from a previous interface /// public int LastModifiedTick { From d80340d2ce77a785fc90dee890ca075e389ede3f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 24 Mar 2025 19:41:50 -0500 Subject: [PATCH 206/236] fix: distributed authority client synchronization issues (#3350) This PR resolves two client synchronization related issues: - When using a distributed authority network topology the session owner can max-out the reliable in-flight messages allowed and start dropping packets when many clients attempt to connect simultaneously. - When scene management was disabled, any client attempting to spawn objects during the initial synchronization would not be allowed to due to the `NetworkManager.IsConnectedClient` not being set until after the client had finished synchronizing (i.e. all objects synchronized had run through the spawn process). fix: #3280 close: #3280 ## Changelog - Fixed: Issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. - Fixed: Issue when using a distributed authority network topology and scene management was disabled clients would not be able to spawn any new network prefab instances until synchronization was complete. ## Testing and Documentation - No test has been added for session owner reaching maximum in-flight reliable messages (requires manual CCU stress test). - Includes the `SpawnDuringSynchronizationTests` integration test. - No documentation changes or additions were necessary. --------- Co-authored-by: Emma --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Messages/ClientConnectedMessage.cs | 2 + .../Messages/ConnectionApprovedMessage.cs | 18 ++- .../SceneManagement/NetworkSceneManager.cs | 70 ++++++++- .../Runtime/Spawning/NetworkSpawnManager.cs | 18 +-- .../SpawnDuringSynchronizationTests.cs | 147 ++++++++++++++++++ .../SpawnDuringSynchronizationTests.cs.meta | 2 + 7 files changed, 241 insertions(+), 18 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 1bffdbce19..18ea7b717e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -18,6 +18,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. (#3350) +- Fixed issue when using a distributed authority network topology and scene management was disabled clients would not be able to spawn any new network prefab instances until synchronization was complete. (#3350) - Fixed issue where the `MaximumInterpolationTime` could not be modified from within the inspector view or runtime. (#3337) - Fixed `ChangeOwnership` changing ownership to clients that are not observers. This also happened with automated object distribution. (#3323) - Fixed issue where `AnticipatedNetworkVariable` previous value returned by `AnticipatedNetworkVariable.OnAuthoritativeValueChanged` is updated correctly on the non-authoritative side. (#3306) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index 0234bc8b67..2d01389cd0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -73,6 +73,8 @@ public void Handle(ref NetworkContext context) /// DANGO-TODO: Determine if this needs to be removed once the service handles object distribution networkManager.RedistributeToClients = true; networkManager.ClientsToRedistribute.Add(ClientId); + + // TODO: We need a client synchronized message or something like that here } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 4f9e0f5453..cb1de9356f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -270,6 +270,13 @@ public void Handle(ref NetworkContext context) // Only if scene management is disabled do we handle NetworkObject synchronization at this point if (!networkManager.NetworkConfig.EnableSceneManagement) { + /// Mark the client being connected before running through the spawning synchronization so we + /// can assure that if a user attempts to spawn something when an already spawned NetworkObject + /// is spawned (during the initial synchronization just below) it will not error out complaining + /// about the player not being connected. + /// The check for this is done within + networkManager.IsConnectedClient = true; + // DANGO-TODO: This is a temporary fix for no DA CMB scene event handling. // We will either use this same concept or provide some way for the CMB state plugin to handle it. if (networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) @@ -292,9 +299,6 @@ public void Handle(ref NetworkContext context) NetworkObject.AddSceneObject(sceneObject, m_ReceivedSceneObjectData, networkManager); } - // Mark the client being connected - networkManager.IsConnectedClient = true; - if (networkManager.AutoSpawnPlayerPrefabClientSide) { networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId); @@ -315,14 +319,14 @@ public void Handle(ref NetworkContext context) if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement) { // Mark the client being connected - networkManager.IsConnectedClient = true; + networkManager.IsConnectedClient = networkManager.ConnectionManager.LocalClient.IsApproved; networkManager.SceneManager.IsRestoringSession = GetIsSessionRestor(); if (!networkManager.SceneManager.IsRestoringSession) { // Synchronize the service with the initial session owner's loaded scenes and spawned objects - networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); + networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId, true); // Spawn any in-scene placed NetworkObjects networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); @@ -334,9 +338,9 @@ public void Handle(ref NetworkContext context) } // Synchronize the service with the initial session owner's loaded scenes and spawned objects - networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId); + networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId, true); - // With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself, + // With scene management enabled and since the session owner doesn't send a scene event synchronize to itself, // we need to notify the session owner that everything should be synchronized/spawned at this time. networkManager.SpawnManager.NotifyNetworkObjectsSynchronized(); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 1d87687e6d..331d5ff112 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1986,6 +1986,14 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) ///
internal Func ExcludeSceneFromSychronization; + /// + /// This is used for distributed authority sessions only and assures that + /// when many clients attempt to connect at the same time they will be + /// handled sequentially so as to not saturate the session owner's maximum + /// reliable messages. + /// + internal List ClientConnectionQueue = new List(); + /// /// Server Side: /// This is used for players that have just had their connection approved and will assure they are synchronized @@ -1994,8 +2002,30 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) /// synchronized. /// /// newly joined client identifier - internal void SynchronizeNetworkObjects(ulong clientId) + /// true only when invoked on a newly connected and approved client. + internal void SynchronizeNetworkObjects(ulong clientId, bool synchronizingService = false) { + // If we are connected to a live service hosted session and we are not doing the initial synchronization for the service... + if (NetworkManager.CMBServiceConnection && !synchronizingService) + { + // then as long as this is a newly connecting client add it to the connecting client queue. + // Otherwise, if this is not a newly connecting client (i.e. it is already in the queue), then go ahead and synchronize + // that client. + if (!ClientConnectionQueue.Contains(clientId)) + { + ClientConnectionQueue.Add(clientId); + // If we are already synchronizing one or more clients, exit early. This client will be synchronized later. + if (ClientConnectionQueue.Count > 1) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.Log($"Deferring Client-{clientId} synchrnization."); + } + return; + } + } + } + // Update the clients NetworkManager.SpawnManager.UpdateObservedNetworkObjects(clientId); @@ -2623,6 +2653,44 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) // DANGO-EXP TODO: Remove this once service distributes objects NetworkManager.SpawnManager.DistributeNetworkObjects(clientId); EndSceneEvent(sceneEventId); + + // Exit early if not a distributed authority session or this is a DAHost + // (DAHost has a unique connection per client, so no need to queue synchronization) + if (!NetworkManager.DistributedAuthorityMode || NetworkManager.DAHost) + { + return; + } + + // Otherwise, this is a session owner that could have pending clients to synchronize + if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + { + // Remove the client that just synchronized + ClientConnectionQueue.Remove(clientId); + + // If we have pending clients to synchronize, then make sure they are still connected + while (ClientConnectionQueue.Count > 0) + { + // If the next client is no longer connected then remove it from the list + if (!NetworkManager.ConnectedClientsIds.Contains(ClientConnectionQueue[0])) + { + ClientConnectionQueue.RemoveAt(0); + } + else + { + break; + } + } + + // If we still have any pending clients waiting, then synchronize the next one + if (ClientConnectionQueue.Count > 0) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.Log($"Synchronizing Client-{ClientConnectionQueue[0]}..."); + } + SynchronizeNetworkObjects(ClientConnectionQueue[0]); + } + } break; } default: diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 7b390547a4..0c8b8d74bd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1815,7 +1815,7 @@ internal void Shutdown() ///
/// the table to populate /// the total number of the specific object type to distribute - internal void GetObjectDistribution(ref Dictionary>> objectByTypeAndOwner, ref Dictionary objectTypeCount) + internal void GetObjectDistribution(ulong clientId, ref Dictionary>> objectByTypeAndOwner, ref Dictionary objectTypeCount) { // DANGO-TODO-MVP: Remove this once the service handles object distribution var onlyIncludeOwnedObjects = NetworkManager.CMBServiceConnection; @@ -1844,6 +1844,11 @@ internal void GetObjectDistribution(ref Dictionary(); // Get all spawned objects by type and then by client owner that are spawned and can be distributed - GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); + GetObjectDistribution(clientId, ref distributedNetworkObjects, ref objectTypeCount); var clientCount = NetworkManager.ConnectedClientsIds.Count; @@ -1951,7 +1956,6 @@ internal void DistributeNetworkObjects(ulong clientId) var maxDistributeCount = Mathf.Max(ownerList.Value.Count - objPerClient, 1); var distributed = 0; - // For now when we have more players then distributed NetworkObjects that // a specific client owns, just assign half of the NetworkObjects to the new client var offsetCount = Mathf.Max((int)Math.Round((float)(ownerList.Value.Count / objPerClient)), 1); @@ -1964,11 +1968,6 @@ internal void DistributeNetworkObjects(ulong clientId) { if ((i % offsetCount) == 0) { - while (!ownerList.Value[i].Observers.Contains(clientId)) - { - i++; - } - var children = ownerList.Value[i].GetComponentsInChildren(); // Since the ownerList.Value[i] has to be distributable, then transfer all child NetworkObjects // with the same owner clientId and are marked as distributable also to the same client to keep @@ -2011,7 +2010,7 @@ internal void DistributeNetworkObjects(ulong clientId) var builder = new StringBuilder(); distributedNetworkObjects.Clear(); objectTypeCount.Clear(); - GetObjectDistribution(ref distributedNetworkObjects, ref objectTypeCount); + GetObjectDistribution(clientId, ref distributedNetworkObjects, ref objectTypeCount); builder.AppendLine($"Client Relative Distributed Object Count: (distribution follows)"); // Cycle through each prefab type foreach (var objectTypeEntry in distributedNetworkObjects) @@ -2026,7 +2025,6 @@ internal void DistributeNetworkObjects(ulong clientId) } Debug.Log(builder.ToString()); } - } internal struct DeferredDespawnObject diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs new file mode 100644 index 0000000000..6c91112259 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs @@ -0,0 +1,147 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(NetworkBehaviourSpawnTimes.OnNetworkSpawn, SceneManagement.Enabled)] + [TestFixture(NetworkBehaviourSpawnTimes.OnNetworkPostSpawn, SceneManagement.Enabled)] + [TestFixture(NetworkBehaviourSpawnTimes.OnSynchronized, SceneManagement.Enabled)] + [TestFixture(NetworkBehaviourSpawnTimes.OnNetworkSpawn, SceneManagement.Disabled)] + [TestFixture(NetworkBehaviourSpawnTimes.OnNetworkPostSpawn, SceneManagement.Disabled)] + [TestFixture(NetworkBehaviourSpawnTimes.OnSynchronized, SceneManagement.Disabled)] + internal class SpawnDuringSynchronizationTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private List m_AllNetworkManagers = new List(); + private StringBuilder m_ErrorLog = new StringBuilder(); + + private NetworkBehaviourSpawnTimes m_SpawnTime; + private bool m_EnableSceneManagement; + internal enum NetworkBehaviourSpawnTimes + { + OnNetworkSpawn, + OnNetworkPostSpawn, + OnSynchronized + } + + internal enum SceneManagement + { + Enabled, + Disabled, + } + + internal class SpawnTestComponent : NetworkBehaviour + { + public GameObject PrefabToSpawn; + public NetworkObject SpawnedObject { get; private set; } + public NetworkBehaviourSpawnTimes SpawnTime; + + private void SpawnObject(NetworkBehaviourSpawnTimes spawnTime) + { + if (IsOwner && SpawnTime == spawnTime) + { + SpawnedObject = NetworkObject.InstantiateAndSpawn(PrefabToSpawn, NetworkManager, OwnerClientId); + } + } + + public override void OnNetworkSpawn() + { + SpawnObject(NetworkBehaviourSpawnTimes.OnNetworkSpawn); + base.OnNetworkSpawn(); + } + + protected override void OnNetworkPostSpawn() + { + SpawnObject(NetworkBehaviourSpawnTimes.OnNetworkPostSpawn); + base.OnNetworkPostSpawn(); + } + + protected override void OnNetworkSessionSynchronized() + { + SpawnObject(NetworkBehaviourSpawnTimes.OnSynchronized); + base.OnNetworkSessionSynchronized(); + } + } + + + public SpawnDuringSynchronizationTests(NetworkBehaviourSpawnTimes spawnTime, SceneManagement sceneManagement) : base(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost) + { + m_SpawnTime = spawnTime; + m_EnableSceneManagement = sceneManagement == SceneManagement.Enabled; + } + + protected override void OnCreatePlayerPrefab() + { + var spawnTestComponent = m_PlayerPrefab.AddComponent(); + spawnTestComponent.SpawnTime = m_SpawnTime; + spawnTestComponent.NetworkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.None); + base.OnCreatePlayerPrefab(); + } + + protected override void OnServerAndClientsCreated() + { + var spawnTestComponent = m_PlayerPrefab.GetComponent(); + spawnTestComponent.PrefabToSpawn = CreateNetworkObjectPrefab("ObjToSpawn"); + spawnTestComponent.PrefabToSpawn.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + if (!UseCMBService()) + { + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + } + foreach (var networkManager in m_ClientNetworkManagers) + { + networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + } + base.OnServerAndClientsCreated(); + } + + private bool AllClientsSpawnedObject() + { + m_ErrorLog.Clear(); + var spawnTestComponent = (SpawnTestComponent)null; + foreach (var networkManager in m_AllNetworkManagers) + { + spawnTestComponent = networkManager.LocalClient.PlayerObject.GetComponent(); + if (spawnTestComponent.SpawnedObject == null || !spawnTestComponent.SpawnedObject.IsSpawned) + { + m_ErrorLog.AppendLine($"{networkManager.name}'s player failed to spawn the network prefab!"); + break; + } + foreach (var networkManagerToCheck in m_AllNetworkManagers) + { + if (networkManagerToCheck == networkManager) + { + continue; + } + if (!networkManagerToCheck.SpawnManager.SpawnedObjects.ContainsKey(spawnTestComponent.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"{networkManager.name}'s player failed to spawn the network prefab!"); + } + } + } + return m_ErrorLog.Length == 0; + } + + /// + /// Validates that a client can spawn network prefabs during OnNetworkSpawn, OnNetworkPostSpawn, and OnNetworkSessionSynchronized. + /// + [UnityTest] + public IEnumerator SpawnDuringSynchronization() + { + m_AllNetworkManagers.Clear(); + m_AllNetworkManagers.AddRange(m_ClientNetworkManagers); + if (!UseCMBService()) + { + m_AllNetworkManagers.Add(m_ServerNetworkManager); + } + + yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject); + AssertOnTimeout(m_ErrorLog.ToString()); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs.meta new file mode 100644 index 0000000000..415940bde8 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/SpawnDuringSynchronizationTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3535334da619ee944a73ca56e2e76d22 \ No newline at end of file From c754a2cac7ad046001182ba7f550fe03ce5532db Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 25 Mar 2025 10:44:55 -0500 Subject: [PATCH 207/236] fix: owner changing ownership causing identical previous and current ids when using a distributed authority network topology (#3347) This fixes an issue with the synchronization of NetworkVariables when changing ownership where it was possible to have the previous owner be equal to the current owner when using a distributed authority network topology and the owner handled changing the ownership. This includes an additional check for ditry states of any collections base NetworkVariables. fix: #3343 close: #3343 ## Changelog - Fixed issue where an owner that changes ownership, when using a distributed authority network topology, could yield identical previous and current owner identifiers. This could also cause `NetworkTransform` to fail to change ownership which would leave the previous owner still subscribed to network tick events. ## Testing and Documentation - Includes integration test `NetworkObjectOwnershipTests.TestAuthorityChangingOwnership`. - No documentation changes or additions were necessary. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkBehaviour.cs | 11 ++- .../Runtime/Core/NetworkBehaviourUpdater.cs | 2 - .../Runtime/Core/NetworkObject.cs | 31 +++++- .../Messages/ChangeOwnershipMessage.cs | 7 +- .../NetworkVariable/NetworkVariable.cs | 7 ++ .../NetworkVariable/NetworkVariableBase.cs | 9 ++ .../Runtime/Spawning/NetworkSpawnManager.cs | 22 +++-- .../NetworkObjectOwnershipTests.cs | 94 +++++++++++++++++++ 9 files changed, 168 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 18ea7b717e..9996ef2d61 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -20,6 +20,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. (#3350) - Fixed issue when using a distributed authority network topology and scene management was disabled clients would not be able to spawn any new network prefab instances until synchronization was complete. (#3350) +- Fixed issue where an owner that changes ownership, when using a distributed authority network topology, could yield identical previous and current owner identifiers. This could also cause `NetworkTransform` to fail to change ownership which would leave the previous owner still subscribed to network tick events. (#3347) - Fixed issue where the `MaximumInterpolationTime` could not be modified from within the inspector view or runtime. (#3337) - Fixed `ChangeOwnership` changing ownership to clients that are not observers. This also happened with automated object distribution. (#3323) - Fixed issue where `AnticipatedNetworkVariable` previous value returned by `AnticipatedNetworkVariable.OnAuthoritativeValueChanged` is updated correctly on the non-authoritative side. (#3306) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 9629550313..29cbff8d2e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1185,7 +1185,12 @@ internal void MarkVariablesDirty(bool dirty) } } - internal void MarkOwnerReadVariablesDirty() + /// + /// For owner read permissions, when changing ownership we need to do a full synchronization + /// of all NetworkVariables that are owner read permission based since the owner is the only + /// instance that knows what the most current values are. + /// + internal void MarkOwnerReadDirtyAndCheckOwnerWriteIsDirty() { for (int j = 0; j < NetworkVariableFields.Count; j++) { @@ -1193,6 +1198,10 @@ internal void MarkOwnerReadVariablesDirty() { NetworkVariableFields[j].SetDirty(true); } + if (NetworkVariableFields[j].WritePerm == NetworkVariableWritePermission.Owner) + { + NetworkVariableFields[j].OnCheckIsDirtyState(); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 6a1dd748e6..b2623cb389 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -108,8 +108,6 @@ internal void NetworkBehaviourUpdate(bool forceSend = false) foreach (var dirtyobj in m_DirtyNetworkObjects) { dirtyobj.PostNetworkVariableWrite(forceSend); - // Once done processing, we set the previous owner id to the current owner id - dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId; } m_DirtyNetworkObjects.Clear(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index a14bd79a2b..51895a15e8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2567,12 +2567,39 @@ internal void MarkVariablesDirty(bool dirty) } } - internal void MarkOwnerReadVariablesDirty() + /// + /// Used when changing ownership, this will mark any owner read permission base NetworkVariables as dirty + /// and will check if any owner write permission NetworkVariables are dirty (primarily for collections) so + /// the new owner will get a full state update prior to changing ownership. + /// + /// + /// We have to pass in the original owner and previous owner to "reset" back to the current state of this + /// NetworkObject in order to preserve the same ownership change flow. By the time this is invoked, the + /// new and previous owner ids have already been set. + /// + /// the owner prior to beginning the change in ownership change. + /// the previous owner prior to beginning the change in ownership change. + internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong originalPreviousOwnerId) { + var currentOwnerId = OwnerClientId; + OwnerClientId = originalOwnerId; + PreviousOwnerId = originalPreviousOwnerId; for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { - ChildNetworkBehaviours[i].MarkOwnerReadVariablesDirty(); + ChildNetworkBehaviours[i].MarkOwnerReadDirtyAndCheckOwnerWriteIsDirty(); } + + // Now set the new owner and previous owner identifiers back to their original new values + // before we run the NetworkBehaviourUpdate. For owner read only permissions this order of + // operations is **particularly important** as we need to first (above) mark things as dirty + // from the context of the original owner and then second (below) we need to send the messages + // which requires the new owner to be set for owner read permission NetworkVariables. + OwnerClientId = currentOwnerId; + PreviousOwnerId = originalOwnerId; + + // Force send a state update for all owner read NetworkVariables and any currently dirty + // owner write NetworkVariables. + NetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(true); } // NGO currently guarantees that the client will receive spawn data for all objects in one network tick. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 998e84d640..9b980d2430 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -381,11 +381,8 @@ private void HandleOwnershipChange(ref NetworkContext context) if (originalOwner == networkManager.LocalClientId && !networkManager.DistributedAuthorityMode) { - // Mark any owner read variables as dirty - networkObject.MarkOwnerReadVariablesDirty(); - // Immediately queue any pending deltas and order the message before the - // change in ownership message. - networkManager.BehaviourUpdater.NetworkBehaviourUpdate(true); + // Fully synchronize NetworkVariables with either read or write ownership permissions. + networkObject.SynchronizeOwnerNetworkVariables(originalOwner, networkObject.PreviousOwnerId); } // Always invoke ownership change notifications diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 4740abd391..66fae6a82a 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -166,6 +166,13 @@ public bool CheckDirtyState(bool forceCheck = false) return isDirty; } + /// + internal override void OnCheckIsDirtyState() + { + CheckDirtyState(); + base.OnCheckIsDirtyState(); + } + internal ref T RefValue() { return ref m_InternalValue; diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index c398e2f87b..e8e7221f78 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -373,6 +373,15 @@ internal ulong OwnerClientId() return m_NetworkBehaviour.NetworkObject.OwnerClientId; } + /// + /// Primarily to check for collections dirty states when doing + /// a fully owner read/write NetworkVariable update. + /// + internal virtual void OnCheckIsDirtyState() + { + + } + /// /// Writes the dirty changes, that is, the changes since the variable was last dirty, to the writer /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 0c8b8d74bd..0e9558f4bc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -432,6 +432,15 @@ internal void RemoveOwnership(NetworkObject networkObject) internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool isAuthorized, bool isRequestApproval = false) { + if (clientId == networkObject.OwnerClientId) + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"[{nameof(NetworkSpawnManager)}][{nameof(ChangeOwnership)}] Attempting to change ownership to Client-{clientId} when the owner is already {networkObject.OwnerClientId}! (Ignoring)"); + } + return; + } + // For client-server: // If ownership changes faster than the latency between the client-server and there are NetworkVariables being updated during ownership changes, // then notify the user they could potentially lose state updates if developer logging is enabled. @@ -530,6 +539,10 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool return; } + // Save the previous owner to work around a bit of a nasty issue with assuring NetworkVariables are serialized when ownership changes + var originalPreviousOwnerId = networkObject.PreviousOwnerId; + var originalOwner = networkObject.OwnerClientId; + // Used to distinguish whether a new owner should receive any currently dirty NetworkVariable updates networkObject.PreviousOwnerId = networkObject.OwnerClientId; @@ -545,13 +558,10 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool // Always notify locally on the server when a new owner is assigned networkObject.InvokeBehaviourOnGainedOwnership(); - if (networkObject.PreviousOwnerId == NetworkManager.LocalClientId) + // If we are the original owner, then we want to synchronize owner read & write NetworkVariables. + if (originalOwner == NetworkManager.LocalClientId) { - // Mark any owner read variables as dirty - networkObject.MarkOwnerReadVariablesDirty(); - // Immediately queue any pending deltas and order the message before the - // change in ownership message. - NetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(true); + networkObject.SynchronizeOwnerNetworkVariables(originalOwner, originalPreviousOwnerId); } var size = 0; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index 6c1fe6bba7..16670bf855 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -25,6 +26,12 @@ public override void OnGainedOwnership() OnGainedOwnershipFired = true; } + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + Assert.True(previous != current, $"[{nameof(OnOwnershipChanged)}][Invalid Parameters] Invoked and the previous ({previous}) equals the current ({current})!"); + base.OnOwnershipChanged(previous, current); + } + public override void OnNetworkSpawn() { if (!SpawnedInstances.ContainsKey(NetworkManager.LocalClientId)) @@ -39,6 +46,12 @@ public void ResetFlags() OnLostOwnershipFired = false; OnGainedOwnershipFired = false; } + + [Rpc(SendTo.Authority)] + public void ChangeOwnershipRpc(RpcParams rpcParams = default) + { + NetworkObject.ChangeOwnership(rpcParams.Receive.SenderClientId); + } } @@ -71,6 +84,7 @@ protected override void OnServerAndClientsCreated() { m_OwnershipPrefab = CreateNetworkObjectPrefab("OnwershipPrefab"); m_OwnershipPrefab.AddComponent(); + m_OwnershipPrefab.AddComponent(); if (m_DistributedAuthority) { m_OwnershipPrefab.GetComponent().SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); @@ -465,7 +479,87 @@ public IEnumerator TestOwnedObjectCounts() yield return WaitForConditionOrTimeOut(ServerHasCorrectClientOwnedObjectCount); AssertOnTimeout($"Server does not have the correct count for all clients spawned {k_NumberOfSpawnedObjects} {nameof(NetworkObject)}s!"); + } + /// + /// Validates that when changing ownership NetworkTransform does not enter into a bad state + /// because the previous and current owner identifiers are the same. For client-server this + /// ends up always being the server, but for distributed authority the authority changes when + /// ownership changes. + /// + [UnityTest] + public IEnumerator TestAuthorityChangingOwnership() + { + var authorityManager = (NetworkManager)null; + var allNetworkManagers = m_ClientNetworkManagers.ToList(); + allNetworkManagers.Add(m_ServerNetworkManager); + + if (m_DistributedAuthority) + { + var authorityId = Random.Range(1, NumberOfClients) - 1; + authorityManager = m_ClientNetworkManagers[authorityId]; + m_OwnershipObject = SpawnObject(m_OwnershipPrefab, authorityManager); + m_OwnershipNetworkObject = m_OwnershipObject.GetComponent(); + } + else + { + authorityManager = m_ServerNetworkManager; + m_OwnershipObject = SpawnObject(m_OwnershipPrefab, m_ServerNetworkManager); + m_OwnershipNetworkObject = m_OwnershipObject.GetComponent(); + } + var ownershipNetworkObjectId = m_OwnershipNetworkObject.NetworkObjectId; + bool WaitForClientsToSpawnNetworkObject() + { + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (!clientNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(ownershipNetworkObjectId)) + { + return false; + } + } + return true; + } + + yield return WaitForConditionOrTimeOut(WaitForClientsToSpawnNetworkObject); + AssertOnTimeout($"Timed out waiting for all clients to spawn the {m_OwnershipNetworkObject.name} {nameof(NetworkObject)} instance!"); + + var currentTargetOwner = (ulong)0; + bool WaitForAllInstancesToChangeOwnership() + { + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (!clientNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(ownershipNetworkObjectId)) + { + return false; + } + if (clientNetworkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId].OwnerClientId != currentTargetOwner) + { + return false; + } + } + return true; + } + + // Change ownership a few times and as long as the previous and current owners are not the same when + // OnOwnershipChanged is invoked then the test passed. + foreach (var networkManager in allNetworkManagers) + { + if (networkManager == authorityManager) + { + continue; + } + var clonedObject = networkManager.SpawnManager.SpawnedObjects[ownershipNetworkObjectId]; + + if (clonedObject.OwnerClientId == networkManager.LocalClientId) + { + continue; + } + + var testComponent = clonedObject.GetComponent(); + testComponent.ChangeOwnershipRpc(); + yield return WaitForAllInstancesToChangeOwnership(); + AssertOnTimeout($"Timed out waiting for all instances to change ownership to Client-{networkManager.LocalClientId}!"); + } } } } From e3189a26d35bb4087aa092f856ee55e5f4fb0ac6 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Wed, 26 Mar 2025 13:11:49 -0400 Subject: [PATCH 208/236] refactor: Remove defines based on UTP version (#3362) There used to be a time when NGO supported both UTP 1.X and 2.X, which led to the use of defines like `UTP_TRANSPORT_2_0_ABOVE` to gate features that were only usable with UTP 2.X (or by proxy to differentiate code that was different for collections 1.X verus 2.X). In some files (like `UnityTransport.cs`) these defines are used abundantly, to the point of impacting code readability. But NGO 2.X now depends on UTP 2.X, so these defines are now useless since they'll always be defined. This PR thus removes them. Functionally speaking this PR should change absolutely nothing since it just removes code that was never compiled anyway. ## Changelog N/A ## Testing and Documentation - No tests have been added (in fact some were removed). - No documentation changes or additions were necessary. --------- Co-authored-by: Noel Stephens --- .../Editor/NetworkManagerRelayIntegration.cs | 4 +- .../Editor/com.unity.netcode.editor.asmdef | 5 - .../Messaging/NetworkMessageManager.cs | 4 - .../CollectionSerializationUtility.cs | 25 +- .../Serialization/NetworkVariableEquality.cs | 19 +- .../Serialization/ResizableBitVector.cs | 8 - .../Runtime/Serialization/FastBufferWriter.cs | 24 - .../Transports/UTP/BatchedReceiveQueue.cs | 12 +- .../Transports/UTP/BatchedSendQueue.cs | 24 - .../Runtime/Transports/UTP/UnityTransport.cs | 170 +----- .../Runtime/com.unity.netcode.runtime.asmdef | 10 - .../Serialization/FastBufferWriterTests.cs | 4 - .../Transports/BatchedReceiveQueueTests.cs | 3 - .../Transports/BatchedSendQueueTests.cs | 3 - .../Editor/Transports/UnityTransportTests.cs | 2 - .../com.unity.netcode.editortests.asmdef | 5 - .../DistributedAuthorityCodecTests.cs | 8 - .../Runtime/Metrics/PacketLossMetricsTests.cs | 7 - .../NetworkTransformPacketLossTests.cs | 501 ------------------ .../NetworkTransformPacketLossTests.cs.meta | 3 - .../NetworkVariable/NetworkVariableTests.cs | 12 - .../Transports/UnityTransportDriverClient.cs | 4 - .../Runtime/Transports/UnityTransportTests.cs | 57 -- .../com.unity.netcode.runtimetests.asmdef | 5 - .../NetworkSceneManagerFixValidationTests.cs | 325 ------------ ...workSceneManagerFixValidationTests.cs.meta | 11 - .../Runtime/testproject.runtimetests.asmdef | 5 - 27 files changed, 25 insertions(+), 1235 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs.meta delete mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs delete mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerRelayIntegration.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerRelayIntegration.cs index a3cfd36d7a..d9d48c003f 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerRelayIntegration.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerRelayIntegration.cs @@ -1,4 +1,4 @@ -#if UNITY_2022_3_OR_NEWER && (RELAY_SDK_INSTALLED && !UNITY_WEBGL ) || (RELAY_SDK_INSTALLED && UNITY_WEBGL && UTP_TRANSPORT_2_0_ABOVE) +#if UNITY_2022_3_OR_NEWER && (RELAY_SDK_INSTALLED && !UNITY_WEBGL ) || (RELAY_SDK_INSTALLED && UNITY_WEBGL) using System; using System.Threading.Tasks; using Unity.Netcode.Transports.UTP; @@ -109,9 +109,7 @@ private static UnityTransport GetUnityTransport(NetworkManager networkManager, s { transport = networkManager.gameObject.AddComponent(); } -#if UTP_TRANSPORT_2_0_ABOVE transport.UseWebSockets = connectionType.StartsWith("ws"); // Probably should be part of SetRelayServerData, but not possible at this point -#endif networkManager.NetworkConfig.NetworkTransport = transport; // Force using UnityTransport return transport; } diff --git a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef index e80328cfad..490c1b2d55 100644 --- a/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef +++ b/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef @@ -49,11 +49,6 @@ "expression": "1.0", "define": "RELAY_SDK_INSTALLED" }, - { - "name": "com.unity.transport", - "expression": "2.0", - "define": "UTP_TRANSPORT_2_0_ABOVE" - }, { "name": "com.unity.services.multiplayer", "expression": "0.2.0", diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 661fbd74f5..8401ed147a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -808,11 +808,7 @@ internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in N internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in NativeList clientIds) where T : INetworkMessage { -#if UTP_TRANSPORT_2_0_ABOVE return SendMessage(ref message, delivery, new PointerListWrapper(clientIds.GetUnsafePtr(), clientIds.Length)); -#else - return SendMessage(ref message, delivery, new PointerListWrapper((ulong*)clientIds.GetUnsafePtr(), clientIds.Length)); -#endif } internal unsafe void ProcessSendQueues() diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs index 912bcf94a0..e8c61ceea7 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs @@ -505,13 +505,8 @@ public static void WriteNativeListDelta(FastBufferWriter writer, ref NativeLi writer.WriteValueSafe(changes); unsafe { -#if UTP_TRANSPORT_2_0_ABOVE var ptr = value.GetUnsafePtr(); var prevPtr = previousValue.GetUnsafePtr(); -#else - var ptr = (T*)value.GetUnsafePtr(); - var prevPtr = (T*)previousValue.GetUnsafePtr(); -#endif for (int i = 0; i < value.Length; ++i) { if (changes.IsSet(i)) @@ -554,11 +549,7 @@ public static void ReadNativeListDelta(FastBufferReader reader, ref NativeLis unsafe { -#if UTP_TRANSPORT_2_0_ABOVE var ptr = value.GetUnsafePtr(); -#else - var ptr = (T*)value.GetUnsafePtr(); -#endif for (var i = 0; i < value.Length; ++i) { if (changes.IsSet(i)) @@ -601,11 +592,8 @@ public static unsafe void WriteNativeHashSetDelta(FastBufferWriter writer, re ++removedCount; } } -#if UTP_TRANSPORT_2_0_ABOVE + if (addedCount + removedCount >= value.Count) -#else - if (addedCount + removedCount >= value.Count()) -#endif { writer.WriteByteSafe(1); writer.WriteValueSafe(value); @@ -655,15 +643,10 @@ public static unsafe void WriteNativeHashMapDelta(FastBufferWriter w where TVal : unmanaged { // See WriteDictionary; this is the same algorithm, adjusted for the NativeHashMap API -#if UTP_TRANSPORT_2_0_ABOVE var added = stackalloc KVPair[value.Count]; var changed = stackalloc KVPair[value.Count]; var removed = stackalloc KVPair[previousValue.Count]; -#else - var added = stackalloc KeyValue[value.Count()]; - var changed = stackalloc KeyValue[value.Count()]; - var removed = stackalloc KeyValue[previousValue.Count()]; -#endif + var addedCount = 0; var changedCount = 0; var removedCount = 0; @@ -692,11 +675,7 @@ public static unsafe void WriteNativeHashMapDelta(FastBufferWriter w } } -#if UTP_TRANSPORT_2_0_ABOVE if (addedCount + removedCount + changedCount >= value.Count) -#else - if (addedCount + removedCount + changedCount >= value.Count()) -#endif { writer.WriteByteSafe(1); writer.WriteValueSafe(value); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs index c8aecb3869..6b0a3c08c0 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableEquality.cs @@ -42,13 +42,8 @@ internal static unsafe bool ValueEqualsList(ref NativeList(ref NativeList< return false; } -#if UTP_TRANSPORT_2_0_ABOVE var aptr = a.GetUnsafePtr(); var bptr = b.GetUnsafePtr(); -#else - var aptr = (TValueType*)a.GetUnsafePtr(); - var bptr = (TValueType*)b.GetUnsafePtr(); -#endif + for (var i = 0; i < a.Length; ++i) { if (!EqualityEquals(ref aptr[i], ref bptr[i])) @@ -246,11 +237,7 @@ internal static bool EqualityEqualsNativeHashSet(ref NativeHashSet a, return true; } -#if UTP_TRANSPORT_2_0_ABOVE if (a.Count != b.Count) -#else - if (a.Count() != b.Count()) -#endif { return false; } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs index b4b4b76e83..03f351d501 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/ResizableBitVector.cs @@ -95,19 +95,11 @@ public unsafe void NetworkSerialize(BufferSerializer serializer) where T : { if (serializer.IsReader) { -#if UTP_TRANSPORT_2_0_ABOVE serializer.GetFastBufferReader().ReadBytesSafe(ptr, length); -#else - serializer.GetFastBufferReader().ReadBytesSafe((byte*)ptr, length); -#endif } else { -#if UTP_TRANSPORT_2_0_ABOVE serializer.GetFastBufferWriter().WriteBytesSafe(ptr, length); -#else - serializer.GetFastBufferWriter().WriteBytesSafe((byte*)ptr, length); -#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index addd102981..381c92f625 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -772,11 +772,7 @@ public unsafe void WriteBytes(NativeArray value, int size = -1, int offset [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytes(NativeList value, int size = -1, int offset = 0) { -#if UTP_TRANSPORT_2_0_ABOVE byte* ptr = value.GetUnsafePtr(); -#else - byte* ptr = (byte*)value.GetUnsafePtr(); -#endif WriteBytes(ptr, size == -1 ? value.Length : size, offset); } @@ -820,11 +816,7 @@ public unsafe void WriteBytesSafe(NativeArray value, int size = -1, int of [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytesSafe(NativeList value, int size = -1, int offset = 0) { -#if UTP_TRANSPORT_2_0_ABOVE byte* ptr = value.GetUnsafePtr(); -#else - byte* ptr = (byte*)value.GetUnsafePtr(); -#endif WriteBytesSafe(ptr, size == -1 ? value.Length : size, offset); } @@ -1015,11 +1007,7 @@ internal unsafe void WriteUnmanagedSafe(NativeArray value) where T : unman internal unsafe void WriteUnmanaged(NativeList value) where T : unmanaged { WriteLength(value.Length); -#if UTP_TRANSPORT_2_0_ABOVE var ptr = value.GetUnsafePtr(); -#else - var ptr = (T*)value.GetUnsafePtr(); -#endif { byte* bytes = (byte*)ptr; WriteBytes(bytes, sizeof(T) * value.Length); @@ -1029,11 +1017,7 @@ internal unsafe void WriteUnmanaged(NativeList value) where T : unmanaged internal unsafe void WriteUnmanagedSafe(NativeList value) where T : unmanaged { WriteLengthSafe(value.Length); -#if UTP_TRANSPORT_2_0_ABOVE var ptr = value.GetUnsafePtr(); -#else - var ptr = (T*)value.GetUnsafePtr(); -#endif { byte* bytes = (byte*)ptr; WriteBytesSafe(bytes, sizeof(T) * value.Length); @@ -1231,11 +1215,7 @@ public void WriteValue(NativeList value, ForGeneric unused = default) wher [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteValueSafe(NativeHashSet value) where T : unmanaged, IEquatable { -#if UTP_TRANSPORT_2_0_ABOVE WriteLengthSafe(value.Count); -#else - WriteLengthSafe(value.Count()); -#endif foreach (var item in value) { var iReffable = item; @@ -1248,11 +1228,7 @@ internal void WriteValueSafe(NativeHashMap value) where TKey : unmanaged, IEquatable where TVal : unmanaged { -#if UTP_TRANSPORT_2_0_ABOVE WriteLengthSafe(value.Count); -#else - WriteLengthSafe(value.Count()); -#endif foreach (var item in value) { (var key, var val) = (item.Key, item.Value); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs index e0cdeebb49..5551c8062f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedReceiveQueue.cs @@ -1,9 +1,7 @@ using System; -using Unity.Networking.Transport; -#if UTP_TRANSPORT_2_0_ABOVE using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; -#endif +using Unity.Networking.Transport; namespace Unity.Netcode.Transports.UTP { @@ -29,11 +27,7 @@ public BatchedReceiveQueue(DataStreamReader reader) { fixed (byte* dataPtr = m_Data) { -#if UTP_TRANSPORT_2_0_ABOVE reader.ReadBytesUnsafe(dataPtr, reader.Length); -#else - reader.ReadBytes(dataPtr, reader.Length); -#endif } } @@ -70,11 +64,7 @@ public void PushReader(DataStreamReader reader) { fixed (byte* dataPtr = m_Data) { -#if UTP_TRANSPORT_2_0_ABOVE reader.ReadBytesUnsafe(dataPtr + m_Offset + m_Length, reader.Length); -#else - reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length); -#endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs index 9e71f36459..cb56bf0026 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs @@ -94,11 +94,7 @@ public void Dispose() /// Write a raw buffer to a DataStreamWriter. private unsafe void WriteBytes(ref DataStreamWriter writer, byte* data, int length) { -#if UTP_TRANSPORT_2_0_ABOVE writer.WriteBytesUnsafe(data, length); -#else - writer.WriteBytes(data, length); -#endif } /// Append data at the tail of the queue. No safety checks. @@ -106,11 +102,7 @@ private void AppendDataAtTail(ArraySegment data) { unsafe { -#if UTP_TRANSPORT_2_0_ABOVE var writer = new DataStreamWriter(m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex); -#else - var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex); -#endif writer.WriteInt(data.Count); @@ -149,11 +141,7 @@ public bool PushMessage(ArraySegment message) { unsafe { -#if UTP_TRANSPORT_2_0_ABOVE UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), m_Data.GetUnsafePtr() + HeadIndex, Length); -#else - UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length); -#endif } TailIndex = Length; @@ -239,11 +227,7 @@ public int FillWriterWithMessages(ref DataStreamWriter writer, int softMaxBytes if (bytesToWrite > softMaxBytes && bytesToWrite <= writer.Capacity) { writer.WriteInt(messageLength); -#if UTP_TRANSPORT_2_0_ABOVE WriteBytes(ref writer, m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); -#else - WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); -#endif return bytesToWrite; } @@ -260,11 +244,7 @@ public int FillWriterWithMessages(ref DataStreamWriter writer, int softMaxBytes if (bytesWritten + bytesToWrite <= softMaxBytes) { writer.WriteInt(messageLength); -#if UTP_TRANSPORT_2_0_ABOVE WriteBytes(ref writer, m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); -#else - WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength); -#endif readerOffset += bytesToWrite; bytesWritten += bytesToWrite; @@ -308,11 +288,7 @@ public int FillWriterWithBytes(ref DataStreamWriter writer, int maxBytes = 0) unsafe { -#if UTP_TRANSPORT_2_0_ABOVE WriteBytes(ref writer, m_Data.GetUnsafePtr() + HeadIndex, copyLength); -#else - WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); -#endif } return copyLength; diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index f08e3cebaa..3bd74f7aab 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -7,23 +7,17 @@ using System; using System.Collections.Generic; -using UnityEngine; -using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent; -using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent; using Unity.Burst; -using Unity.Collections.LowLevel.Unsafe; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Networking.Transport; using Unity.Networking.Transport.Relay; -using Unity.Networking.Transport.Utilities; -#if UTP_TRANSPORT_2_0_ABOVE using Unity.Networking.Transport.TLS; -#endif - -#if !UTP_TRANSPORT_2_0_ABOVE -using NetworkEndpoint = Unity.Networking.Transport.NetworkEndPoint; -#endif +using Unity.Networking.Transport.Utilities; +using UnityEngine; +using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent; +using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent; namespace Unity.Netcode.Transports.UTP { @@ -166,29 +160,30 @@ private enum State [SerializeField] private ProtocolType m_ProtocolType; -#if UTP_TRANSPORT_2_0_ABOVE [Tooltip("Per default the client/server will communicate over UDP. Set to true to communicate with WebSocket.")] [SerializeField] private bool m_UseWebSockets = false; + /// Whether to use WebSockets as the protocol of communication. Default is UDP. public bool UseWebSockets { get => m_UseWebSockets; set => m_UseWebSockets = value; } - /// - /// Per default the client/server communication will not be encrypted. Select true to enable DTLS for UDP and TLS for Websocket. - /// [Tooltip("Per default the client/server communication will not be encrypted. Select true to enable DTLS for UDP and TLS for Websocket.")] [SerializeField] private bool m_UseEncryption = false; + + /// + /// Whether to use encryption (default is false). Note that unless using Unity Relay, encryption requires + /// providing certificate information with and . + /// public bool UseEncryption { get => m_UseEncryption; set => m_UseEncryption = value; } -#endif [Tooltip("The maximum amount of packets that can be in the internal send/receive queues. Basically this is how many packets can be sent/received in a single update/frame.")] [SerializeField] @@ -406,10 +401,8 @@ public struct SimulatorParameters /// - packet drop rate (packet loss) ///
-#if UTP_TRANSPORT_2_0_ABOVE [Obsolete("DebugSimulator is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)] [HideInInspector] -#endif public SimulatorParameters DebugSimulator = new SimulatorParameters { PacketDelayMS = 0, @@ -732,11 +725,7 @@ public void SetConnectionData(NetworkEndpoint endPoint, NetworkEndpoint listenEn /// Packet delay in milliseconds. /// Packet jitter in milliseconds. /// Packet drop percentage. - -#if UTP_TRANSPORT_2_0_ABOVE [Obsolete("SetDebugSimulatorParameters is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)] -#endif - public void SetDebugSimulatorParameters(int packetDelay, int packetJitter, int dropRate) { if (m_Driver.IsCreated) @@ -1068,11 +1057,7 @@ private void ExtractNetworkMetricsFromPipeline(NetworkPipeline pipeline, Network //Don't need to dispose of the buffers, they are filled with data pointers. m_Driver.GetPipelineBuffers(pipeline, -#if UTP_TRANSPORT_2_0_ABOVE NetworkPipelineStageId.Get(), -#else - NetworkPipelineStageCollection.GetStageId(typeof(NetworkMetricsPipelineStage)), -#endif networkConnection, out _, out _, @@ -1099,11 +1084,7 @@ private int ExtractRtt(NetworkConnection networkConnection) } m_Driver.GetPipelineBuffers(m_ReliableSequencedPipeline, -#if UTP_TRANSPORT_2_0_ABOVE NetworkPipelineStageId.Get(), -#else - NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)), -#endif networkConnection, out _, out _, @@ -1125,11 +1106,7 @@ private float ExtractPacketLoss(NetworkConnection networkConnection) } m_Driver.GetPipelineBuffers(m_ReliableSequencedPipeline, -#if UTP_TRANSPORT_2_0_ABOVE NetworkPipelineStageId.Get(), -#else - NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)), -#endif networkConnection, out _, out _, @@ -1342,18 +1319,9 @@ public override void Initialize(NetworkManager networkManager = null) // we sometimes notice a lot of useless resends, especially if using Relay. (We can // only do this with UTP 2.0 because 1.X doesn't support that parameter.) m_NetworkSettings.WithReliableStageParameters( - windowSize: 64 -#if UTP_TRANSPORT_2_0_ABOVE - , + windowSize: 64, maximumResendTime: m_ProtocolType == ProtocolType.RelayUnityTransport ? 750 : 500 -#endif ); - -#if !UTP_TRANSPORT_2_0_ABOVE && !UNITY_WEBGL - m_NetworkSettings.WithBaselibNetworkInterfaceParameters( - receiveQueueCapacity: m_MaxPacketQueueSize, - sendQueueCapacity: m_MaxPacketQueueSize); -#endif } /// @@ -1553,8 +1521,7 @@ public override void Shutdown() m_ServerClientId = 0; } -#if UTP_TRANSPORT_2_0_ABOVE - private void ConfigureSimulatorForUtp2() + private void ConfigureSimulator() { // As DebugSimulator is deprecated, the 'packetDelayMs', 'packetJitterMs' and 'packetDropPercentage' // parameters are set to the default and are supposed to be changed using Network Simulator tool instead. @@ -1570,19 +1537,7 @@ private void ConfigureSimulatorForUtp2() m_NetworkSettings.WithNetworkSimulatorParameters(); } -#else - private void ConfigureSimulatorForUtp1() - { - m_NetworkSettings.WithSimulatorStageParameters( - maxPacketCount: 300, // TODO Is there any way to compute a better value? - maxPacketSize: NetworkParameterConstants.MTU, - packetDelayMs: DebugSimulator.PacketDelayMS, - packetJitterMs: DebugSimulator.PacketJitterMS, - packetDropPercentage: DebugSimulator.PacketDropRate, - randomSeed: DebugSimulatorRandomSeed ?? (uint)System.Diagnostics.Stopwatch.GetTimestamp() - ); - } -#endif + /// protected override NetworkTopologyTypes OnCurrentTopology() { @@ -1641,24 +1596,15 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, out NetworkPipeline unreliableSequencedFragmentedPipeline, out NetworkPipeline reliableSequencedPipeline) { -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 && !UTP_TRANSPORT_2_0_ABOVE - NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage()); -#endif - -#if UTP_TRANSPORT_2_0_ABOVE && UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED - ConfigureSimulatorForUtp2(); -#elif !UTP_TRANSPORT_2_0_ABOVE && (UNITY_EDITOR || DEVELOPMENT_BUILD) - ConfigureSimulatorForUtp1(); +#if UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED + ConfigureSimulator(); #endif - m_NetworkSettings.WithNetworkConfigParameters( maxConnectAttempts: transport.m_MaxConnectAttempts, connectTimeoutMS: transport.m_ConnectTimeoutMS, disconnectTimeoutMS: transport.m_DisconnectTimeoutMS, -#if UTP_TRANSPORT_2_0_ABOVE sendQueueCapacity: m_MaxPacketQueueSize, receiveQueueCapacity: m_MaxPacketQueueSize, -#endif heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); #if UNITY_WEBGL && !UNITY_EDITOR @@ -1683,7 +1629,6 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } #endif -#if UTP_TRANSPORT_2_0_ABOVE if (m_UseEncryption) { if (m_ProtocolType == ProtocolType.RelayUnityTransport) @@ -1725,9 +1670,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } } } -#endif -#if UTP_TRANSPORT_2_1_ABOVE if (m_ProtocolType == ProtocolType.RelayUnityTransport) { if (m_UseWebSockets && m_RelayServerData.IsWebSocket == 0) @@ -1740,9 +1683,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, Debug.LogError("Relay server data indicates usage of WebSockets, but \"Use WebSockets\" checkbox isn't checked under \"Unity Transport\" component."); } } -#endif -#if UTP_TRANSPORT_2_0_ABOVE if (m_UseWebSockets) { driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings); @@ -1756,88 +1697,18 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, driver = NetworkDriver.Create(new UDPNetworkInterface(), m_NetworkSettings); #endif } -#else - driver = NetworkDriver.Create(m_NetworkSettings); -#endif -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 && UTP_TRANSPORT_2_0_ABOVE +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 driver.RegisterPipelineStage(new NetworkMetricsPipelineStage()); #endif -#if !UTP_TRANSPORT_2_0_ABOVE - SetupPipelinesForUtp1(driver, + SetupPipelines(driver, out unreliableFragmentedPipeline, out unreliableSequencedFragmentedPipeline, out reliableSequencedPipeline); -#else - SetupPipelinesForUtp2(driver, - out unreliableFragmentedPipeline, - out unreliableSequencedFragmentedPipeline, - out reliableSequencedPipeline); -#endif } -#if !UTP_TRANSPORT_2_0_ABOVE - private void SetupPipelinesForUtp1(NetworkDriver driver, - out NetworkPipeline unreliableFragmentedPipeline, - out NetworkPipeline unreliableSequencedFragmentedPipeline, - out NetworkPipeline reliableSequencedPipeline) - { -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (DebugSimulator.PacketDelayMS > 0 || DebugSimulator.PacketDropRate > 0) - { - unreliableFragmentedPipeline = driver.CreatePipeline( - typeof(FragmentationPipelineStage), - typeof(SimulatorPipelineStage), - typeof(SimulatorPipelineStageInSend) -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - , typeof(NetworkMetricsPipelineStage) -#endif - ); - unreliableSequencedFragmentedPipeline = driver.CreatePipeline( - typeof(FragmentationPipelineStage), - typeof(UnreliableSequencedPipelineStage), - typeof(SimulatorPipelineStage), - typeof(SimulatorPipelineStageInSend) -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - , typeof(NetworkMetricsPipelineStage) -#endif - ); - reliableSequencedPipeline = driver.CreatePipeline( - typeof(ReliableSequencedPipelineStage), - typeof(SimulatorPipelineStage), - typeof(SimulatorPipelineStageInSend) -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - , typeof(NetworkMetricsPipelineStage) -#endif - ); - } - else -#endif - { - unreliableFragmentedPipeline = driver.CreatePipeline( - typeof(FragmentationPipelineStage) -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - , typeof(NetworkMetricsPipelineStage) -#endif - ); - unreliableSequencedFragmentedPipeline = driver.CreatePipeline( - typeof(FragmentationPipelineStage), - typeof(UnreliableSequencedPipelineStage) -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - , typeof(NetworkMetricsPipelineStage) -#endif - ); - reliableSequencedPipeline = driver.CreatePipeline( - typeof(ReliableSequencedPipelineStage) -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - , typeof(NetworkMetricsPipelineStage) -#endif - ); - } - } -#else - private void SetupPipelinesForUtp2(NetworkDriver driver, + private void SetupPipelines(NetworkDriver driver, out NetworkPipeline unreliableFragmentedPipeline, out NetworkPipeline unreliableSequencedFragmentedPipeline, out NetworkPipeline reliableSequencedPipeline) @@ -1874,9 +1745,8 @@ private void SetupPipelinesForUtp2(NetworkDriver driver, #endif ); } -#endif - // -------------- Utility Types ------------------------------------------------------------------------------- + // -------------- Utility Types ------------------------------------------------------------------------------- /// /// Cached information about reliability mode with a certain client diff --git a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef index f1ff7ec13c..d68a562768 100644 --- a/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef +++ b/com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef @@ -38,16 +38,6 @@ "expression": "1.0.0-pre.7", "define": "MULTIPLAYER_TOOLS_1_0_0_PRE_7" }, - { - "name": "com.unity.transport", - "expression": "2.0.0-exp", - "define": "UTP_TRANSPORT_2_0_ABOVE" - }, - { - "name": "com.unity.transport", - "expression": "2.1.0", - "define": "UTP_TRANSPORT_2_1_ABOVE" - }, { "name": "Unity", "expression": "2023", diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs index 98d423169e..53673d5742 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferWriterTests.cs @@ -311,11 +311,7 @@ private unsafe void VerifyArrayEquality(NativeList value, byte* unsafePtr, { int* sizeValue = (int*)(unsafePtr + offset); Assert.AreEqual(value.Length, *sizeValue); -#if UTP_TRANSPORT_2_0_ABOVE var asTPointer = value.GetUnsafePtr(); -#else - var asTPointer = (T*)value.GetUnsafePtr(); -#endif var underlyingTArray = (T*)(unsafePtr + sizeof(int) + offset); for (var i = 0; i < value.Length; ++i) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs index fef8a3b646..97671f906f 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedReceiveQueueTests.cs @@ -2,9 +2,6 @@ using NUnit.Framework; using Unity.Collections; using Unity.Netcode.Transports.UTP; -#if !UTP_TRANSPORT_2_0_ABOVE -using Unity.Networking.Transport; -#endif namespace Unity.Netcode.EditorTests { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs index 3ad4de1aa8..f27bef95d3 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs @@ -2,9 +2,6 @@ using NUnit.Framework; using Unity.Collections; using Unity.Netcode.Transports.UTP; -#if !UTP_TRANSPORT_2_0_ABOVE -using Unity.Networking.Transport; -#endif namespace Unity.Netcode.EditorTests { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index 3bf49b64fb..87ffbdb418 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -169,7 +169,6 @@ public void UnityTransport_StartClientFailsWithBadAddress() transport.Shutdown(); } -#if UTP_TRANSPORT_2_0_ABOVE [Test] public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] string cert, [Values("", null)] string secret) { @@ -202,6 +201,5 @@ public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] st } } } -#endif } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef index 56d2377536..b2c942a948 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef @@ -33,11 +33,6 @@ "name": "Unity", "expression": "(0,2022.2.0a5)", "define": "UNITY_UNET_PRESENT" - }, - { - "name": "com.unity.transport", - "expression": "2.0.0-exp", - "define": "UTP_TRANSPORT_2_0_ABOVE" } ] } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index 2947b5830f..af69df9aa8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -7,9 +7,7 @@ using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.Transports.UTP; -#if UTP_TRANSPORT_2_0_ABOVE using Unity.Networking.Transport; -#endif using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; @@ -73,9 +71,6 @@ public void TestAuthorityRpc(byte[] _) protected override void OnOneTimeSetup() { // Prevents the tests from running if no CMB Service is detected -#if !UTP_TRANSPORT_2_0_ABOVE - Assert.Ignore("ignoring DA codec tests because UTP transport must be 2.0"); -#else if (!CanConnectToServer(m_TransportHost, k_TransportPort)) { var shouldFail = Environment.GetEnvironmentVariable("ENSURE_CODEC_TESTS"); @@ -88,7 +83,6 @@ protected override void OnOneTimeSetup() Assert.Fail($"Failed to connect to the rust echo-server at {m_TransportHost}:{k_TransportPort}"); } } -#endif base.OnOneTimeSetup(); } @@ -611,7 +605,6 @@ private IEnumerator SendMessage(ref T message) where T : INetworkMessage return m_ClientCodecHook.WaitForMessageReceived(message); } -#if UTP_TRANSPORT_2_0_ABOVE private static bool CanConnectToServer(string host, ushort port, double timeoutMs = 100) { var address = Dns.GetHostAddresses(host).First(); @@ -636,7 +629,6 @@ private static bool CanConnectToServer(string host, ushort port, double timeoutM driver.Disconnect(connection); return true; } -#endif } internal class CodecTestHooks : INetworkHooks diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index f02c0b2467..2956f48fda 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -7,9 +7,7 @@ using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime.Metrics; using Unity.Netcode.Transports.UTP; -#if UTP_TRANSPORT_2_0_ABOVE using Unity.Networking.Transport.Utilities; -#endif using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics @@ -28,9 +26,6 @@ public PacketLossMetricsTests() protected override void OnServerAndClientsCreated() { var clientTransport = (UnityTransport)m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport; -#if !UTP_TRANSPORT_2_0_ABOVE - clientTransport.SetDebugSimulatorParameters(0, 0, m_PacketLossRate); -#endif // Determined through trial and error. With both UTP 1.2 and 2.0, this random seed // results in an effective packet loss percentage between 22% and 28%. Future UTP @@ -68,12 +63,10 @@ public IEnumerator TrackPacketLossAsClient() double packetLossRateMaxrange = (m_PacketLossRate + m_PacketLossRangeDelta) / 100d; var clientNetworkManager = m_ClientNetworkManagers[0]; -#if UTP_TRANSPORT_2_0_ABOVE var clientTransport = (UnityTransport)clientNetworkManager.NetworkConfig.NetworkTransport; clientTransport.NetworkDriver.CurrentSettings.TryGet(out var parameters); parameters.PacketDropPercentage = m_PacketLossRate; clientTransport.NetworkDriver.ModifySimulatorStageParameters(parameters); -#endif var waitForPacketLossMetric = new WaitForGaugeMetricValues((clientNetworkManager.NetworkMetrics as NetworkMetrics).Dispatcher, NetworkMetricTypes.PacketLoss, diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs deleted file mode 100644 index 480905c68b..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs +++ /dev/null @@ -1,501 +0,0 @@ -// TODO: Rewrite test to use the tools package. Debug simulator not available in UTP 2.X. -#if !UTP_TRANSPORT_2_0_ABOVE -using System.Collections; -using NUnit.Framework; -using Unity.Netcode.Components; -using UnityEngine; -using UnityEngine.TestTools; - -namespace Unity.Netcode.RuntimeTests -{ - /// - /// Integration tests for NetworkTransform that will test both - /// server and host operating modes and will test both authoritative - /// models for each operating mode when packet loss and latency is - /// present. - /// - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] - internal class NetworkTransformPacketLossTests : NetworkTransformBase - { - private const int k_Latency = 50; - private const int k_PacketLoss = 2; - - private Vector3 m_RandomPosition; - private Vector3 m_TeleportOffset = new Vector3(-1024f, 0f, 0f); - private bool m_Teleported; - - /// - /// Constructor - /// - /// Determines if we are running as a server or host - /// Determines if we are using server or owner authority - public NetworkTransformPacketLossTests(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) : - base(testWithHost, authority, rotationCompression, rotation, precision) - { } - - protected override void OnServerAndClientsCreated() - { - base.OnServerAndClientsCreated(); - - var unityTransport = m_ServerNetworkManager.NetworkConfig.NetworkTransport as Transports.UTP.UnityTransport; - unityTransport.SetDebugSimulatorParameters(k_Latency, 0, k_PacketLoss); - } - - /// - /// Handles validating all children of the test objects have matching local and global space vaues. - /// - private IEnumerator AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTransformCheckType checkType) - { - // We don't assert on timeout here because we want to log this information during PostAllChildrenLocalTransformValuesMatch - yield return WaitForConditionOrTimeOut(() => AllInstancesKeptLocalTransformValues(useSubChild)); - var success = true; - if (s_GlobalTimeoutHelper.TimedOut) - { - var waitForMs = new WaitForSeconds(0.0025f); - if (m_Precision == Precision.Half) - { - m_CurrentHalfPrecision = 0.2156f; - } - // If we timed out, then wait for a full range of ticks to assure all data has been synchronized before declaring this a failed test. - for (int j = 0; j < m_ServerNetworkManager.NetworkConfig.TickRate; j++) - { - m_InfoMessage.Clear(); - m_InfoMessage.AppendLine($"[{checkType}][{useSubChild}] Timed out waiting for all children to have the correct local space values:\n"); - var instances = useSubChild ? ChildObjectComponent.SubInstances : ChildObjectComponent.Instances; - success = PostAllChildrenLocalTransformValuesMatch(useSubChild); - yield return waitForMs; - } - } - - if (!success) - { - Assert.True(success, m_InfoMessage.ToString()); - } - } - - /// - /// Validates that transform values remain the same when a NetworkTransform is - /// parented under another NetworkTransform under all of the possible axial conditions - /// as well as when the parent has a varying scale. - /// - [UnityTest] - public IEnumerator ParentedNetworkTransformTest([Values] Interpolation interpolation, [Values] bool worldPositionStays, [Values(0.5f, 1.0f, 5.0f)] float scale) - { - ChildObjectComponent.EnableChildLog = m_EnableVerboseDebug; - if (m_EnableVerboseDebug) - { - ChildObjectComponent.TestCount++; - } - // Get the NetworkManager that will have authority in order to spawn with the correct authority - var isServerAuthority = m_Authority == Authority.ServerAuthority; - var authorityNetworkManager = m_ServerNetworkManager; - if (!isServerAuthority) - { - authorityNetworkManager = m_ClientNetworkManagers[0]; - } - - // Spawn a parent and children - ChildObjectComponent.HasSubChild = true; - var serverSideParent = SpawnObject(m_ParentObject.gameObject, authorityNetworkManager).GetComponent(); - var serverSideChild = SpawnObject(m_ChildObject.gameObject, authorityNetworkManager).GetComponent(); - var serverSideSubChild = SpawnObject(m_SubChildObject.gameObject, authorityNetworkManager).GetComponent(); - - yield return s_DefaultWaitForTick; - yield return s_DefaultWaitForTick; - yield return s_DefaultWaitForTick; - yield return s_DefaultWaitForTick; - - // Assure all of the child object instances are spawned before proceeding to parenting - yield return WaitForConditionOrTimeOut(AllChildObjectInstancesAreSpawned); - AssertOnTimeout("Timed out waiting for all child instances to be spawned!"); - - // Get the authority parent and child instances - m_AuthorityParentObject = NetworkTransformTestComponent.AuthorityInstance.NetworkObject; - m_AuthorityChildObject = ChildObjectComponent.AuthorityInstance.NetworkObject; - m_AuthoritySubChildObject = ChildObjectComponent.AuthoritySubInstance.NetworkObject; - - // The child NetworkTransform will use world space when world position stays and - // local space when world position does not stay when parenting. - ChildObjectComponent.AuthorityInstance.InLocalSpace = !worldPositionStays; - ChildObjectComponent.AuthorityInstance.UseHalfFloatPrecision = m_Precision == Precision.Half; - ChildObjectComponent.AuthorityInstance.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; - ChildObjectComponent.AuthorityInstance.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; - - ChildObjectComponent.AuthoritySubInstance.InLocalSpace = !worldPositionStays; - ChildObjectComponent.AuthoritySubInstance.UseHalfFloatPrecision = m_Precision == Precision.Half; - ChildObjectComponent.AuthoritySubInstance.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; - ChildObjectComponent.AuthoritySubInstance.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; - - // Set whether we are interpolating or not - m_AuthorityParentNetworkTransform = m_AuthorityParentObject.GetComponent(); - m_AuthorityParentNetworkTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - m_AuthorityChildNetworkTransform = m_AuthorityChildObject.GetComponent(); - m_AuthorityChildNetworkTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - m_AuthoritySubChildNetworkTransform = m_AuthoritySubChildObject.GetComponent(); - m_AuthoritySubChildNetworkTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - - - // Apply a scale to the parent object to make sure the scale on the child is properly updated on - // non-authority instances. - var halfScale = scale * 0.5f; - m_AuthorityParentObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale); - m_AuthorityChildObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale); - m_AuthoritySubChildObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale); - - // Allow one tick for authority to update these changes - - yield return WaitForConditionOrTimeOut(PositionRotationScaleMatches); - - AssertOnTimeout("All transform values did not match prior to parenting!"); - - // Parent the child under the parent with the current world position stays setting - Assert.True(serverSideChild.TrySetParent(serverSideParent.transform, worldPositionStays), "[Server-Side Child] Failed to set child's parent!"); - - // Parent the sub-child under the child with the current world position stays setting - Assert.True(serverSideSubChild.TrySetParent(serverSideChild.transform, worldPositionStays), "[Server-Side SubChild] Failed to set sub-child's parent!"); - - // This waits for all child instances to be parented - yield return WaitForConditionOrTimeOut(AllChildObjectInstancesHaveChild); - AssertOnTimeout("Timed out waiting for all instances to have parented a child!"); - var latencyWait = new WaitForSeconds(k_Latency * 0.003f); - // Wait for at least 3x designated latency period - yield return latencyWait; - - // This validates each child instance has preserved their local space values - yield return AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Connected_Clients); - - // This validates each sub-child instance has preserved their local space values - yield return AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Connected_Clients); - - // Verify that a late joining client will synchronize to the parented NetworkObjects properly - yield return CreateAndStartNewClient(); - - // Assure all of the child object instances are spawned (basically for the newly connected client) - yield return WaitForConditionOrTimeOut(AllChildObjectInstancesAreSpawned); - AssertOnTimeout("Timed out waiting for all child instances to be spawned!"); - - // This waits for all child instances to be parented - yield return WaitForConditionOrTimeOut(AllChildObjectInstancesHaveChild); - AssertOnTimeout("Timed out waiting for all instances to have parented a child!"); - - // Wait for at least 3x designated latency period - yield return latencyWait; - - // This validates each child instance has preserved their local space values - yield return AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Late_Join_Client); - - // This validates each sub-child instance has preserved their local space values - yield return AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Late_Join_Client); - } - - /// - /// This validates that multiple changes can occur within the same tick or over - /// several ticks while still keeping non-authoritative instances synchronized. - /// - /// - /// When testing < 3 axis: Interpolation is disabled and only 3 delta updates are applied per unique test - /// When testing 3 axis: Interpolation is enabled, sometimes an axis is intentionally excluded during a - /// delta update, and it runs through 8 delta updates per unique test. - /// - [UnityTest] - public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] Axis axis) - { - yield return s_DefaultWaitForTick; - // Just test for OverrideState.Update (they are already being tested for functionality in normal NetworkTransformTests) - var overideState = OverrideState.Update; - var tickRelativeTime = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate); - m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; - bool axisX = axis == Axis.X || axis == Axis.XY || axis == Axis.XZ || axis == Axis.XYZ; - bool axisY = axis == Axis.Y || axis == Axis.XY || axis == Axis.YZ || axis == Axis.XYZ; - bool axisZ = axis == Axis.Z || axis == Axis.XZ || axis == Axis.YZ || axis == Axis.XYZ; - - var axisCount = axisX ? 1 : 0; - axisCount += axisY ? 1 : 0; - axisCount += axisZ ? 1 : 0; - - m_AuthoritativeTransform.StatePushed = false; - // Enable interpolation when all 3 axis are selected to make sure we are synchronizing properly - // when interpolation is enabled. - m_AuthoritativeTransform.Interpolate = axisCount == 3 ? true : false; - - m_CurrentAxis = axis; - - // Authority dictates what is synchronized and what the precision is going to be - // so we only need to set this on the authoritative side. - m_AuthoritativeTransform.UseHalfFloatPrecision = m_Precision == Precision.Half; - m_AuthoritativeTransform.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; - m_AuthoritativeTransform.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; - - m_AuthoritativeTransform.SyncPositionX = axisX; - m_AuthoritativeTransform.SyncPositionY = axisY; - m_AuthoritativeTransform.SyncPositionZ = axisZ; - - if (!m_AuthoritativeTransform.UseQuaternionSynchronization) - { - m_AuthoritativeTransform.SyncRotAngleX = axisX; - m_AuthoritativeTransform.SyncRotAngleY = axisY; - m_AuthoritativeTransform.SyncRotAngleZ = axisZ; - } - else - { - // This is not required for usage (setting the value should not matter when quaternion synchronization is enabled) - // but is required for this test so we don't get a failure on an axis that is marked to not be synchronized when - // validating the authority's values on non-authority instances. - m_AuthoritativeTransform.SyncRotAngleX = true; - m_AuthoritativeTransform.SyncRotAngleY = true; - m_AuthoritativeTransform.SyncRotAngleZ = true; - } - - m_AuthoritativeTransform.SyncScaleX = axisX; - m_AuthoritativeTransform.SyncScaleY = axisY; - m_AuthoritativeTransform.SyncScaleZ = axisZ; - - var positionStart = GetRandomVector3(0.25f, 1.75f); - var rotationStart = GetRandomVector3(1f, 15f); - var scaleStart = GetRandomVector3(0.25f, 2.0f); - var position = positionStart; - var rotation = rotationStart; - var scale = scaleStart; - var success = false; - - - // Wait for the deltas to be pushed - yield return WaitForConditionOrTimeOut(() => m_AuthoritativeTransform.StatePushed); - - // Just in case we drop the first few state updates - if (s_GlobalTimeoutHelper.TimedOut) - { - // Set the local state to not reflect the authority state's local space settings - // to trigger the state update (it would eventually get there, but this is an integration test) - var state = m_AuthoritativeTransform.LocalAuthoritativeNetworkState; - state.InLocalSpace = !m_AuthoritativeTransform.InLocalSpace; - m_AuthoritativeTransform.LocalAuthoritativeNetworkState = state; - // Wait for the deltas to be pushed - yield return WaitForConditionOrTimeOut(() => m_AuthoritativeTransform.StatePushed); - } - AssertOnTimeout("State was never pushed!"); - - // Allow the precision settings to propagate first as changing precision - // causes a teleport event to occur - yield return s_DefaultWaitForTick; - yield return s_DefaultWaitForTick; - yield return s_DefaultWaitForTick; - yield return s_DefaultWaitForTick; - yield return s_DefaultWaitForTick; - var iterations = axisCount == 3 ? k_PositionRotationScaleIterations3Axis : k_PositionRotationScaleIterations; - - // Move and rotate within the same tick, validate the non-authoritative instance updates - // to each set of changes. Repeat several times. - for (int i = 0; i < iterations; i++) - { - // Always reset this per delta update pass - m_AxisExcluded = false; - var deltaPositionDelta = GetRandomVector3(-1.5f, 1.5f); - var deltaRotationDelta = GetRandomVector3(-3.5f, 3.5f); - var deltaScaleDelta = GetRandomVector3(-0.5f, 0.5f); - - m_NonAuthoritativeTransform.StateUpdated = false; - m_AuthoritativeTransform.StatePushed = false; - - // With two or more axis, excluding one of them while chaging another will validate that - // full precision updates are maintaining their target state value(s) to interpolate towards - if (axisCount == 3) - { - position += RandomlyExcludeAxis(deltaPositionDelta); - rotation += RandomlyExcludeAxis(deltaRotationDelta); - scale += RandomlyExcludeAxis(deltaScaleDelta); - } - else - { - position += deltaPositionDelta; - rotation += deltaRotationDelta; - scale += deltaScaleDelta; - } - - // Apply delta between ticks - MoveRotateAndScaleAuthority(position, rotation, scale, overideState); - - // Wait for the deltas to be pushed (unlike the original test, we don't wait for state to be updated as that could be dropped here) - yield return WaitForConditionOrTimeOut(() => m_AuthoritativeTransform.StatePushed); - AssertOnTimeout($"[Non-Interpolate {i}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed})!"); - - // For 3 axis, we will skip validating that the non-authority interpolates to its target point at least once. - // This will validate that non-authoritative updates are maintaining their target state axis values if only 2 - // of the axis are being updated to assure interpolation maintains the targeted axial value per axis. - // For 2 and 1 axis tests we always validate per delta update - if (m_AxisExcluded || axisCount < 3) - { - // Wait for deltas to synchronize on non-authoritative side - yield return WaitForConditionOrTimeOut(PositionRotationScaleMatches); - // Provide additional debug info about what failed (if it fails) - if (s_GlobalTimeoutHelper.TimedOut) - { - Debug.Log("[Synch Issue Start - 1]"); - // If we timed out, then wait for a full range of ticks (plus 1) to assure it sent synchronization data. - for (int j = 0; j < m_ServerNetworkManager.NetworkConfig.TickRate * 2; j++) - { - success = PositionRotationScaleMatches(); - if (success) - { - // If we matched, then something was dropped and recovered when synchronized - break; - } - yield return s_DefaultWaitForTick; - } - - // Only if we still didn't match - if (!success) - { - m_EnableVerboseDebug = true; - success = PositionRotationScaleMatches(); - m_EnableVerboseDebug = false; - Debug.Log("[Synch Issue END - 1]"); - AssertOnTimeout($"[Non-Interpolate {i}] Timed out waiting for non-authority to match authority's position or rotation"); - } - } - } - } - - if (axisCount == 3) - { - // As a final test, wait for deltas to synchronize on non-authoritative side to assure it interpolates to the correct values - yield return WaitForConditionOrTimeOut(PositionRotationScaleMatches); - // Provide additional debug info about what failed (if it fails) - if (s_GlobalTimeoutHelper.TimedOut) - { - Debug.Log("[Synch Issue Start - 2]"); - // If we timed out, then wait for a full range of ticks (plus 1) to assure it sent synchronization data. - for (int j = 0; j < m_ServerNetworkManager.NetworkConfig.TickRate * 2; j++) - { - success = PositionRotationScaleMatches(); - if (success) - { - // If we matched, then something was dropped and recovered when synchronized - break; - } - yield return s_DefaultWaitForTick; - } - - // Only if we still didn't match - if (!success) - { - m_EnableVerboseDebug = true; - PositionRotationScaleMatches(); - m_EnableVerboseDebug = false; - Debug.Log("[Synch Issue END - 2]"); - AssertOnTimeout("Timed out waiting for non-authority to match authority's position or rotation"); - - } - } - - } - } - - /// - /// Tests changing all axial values one at a time with packet loss - /// These tests are performed: - /// - While in local space and world space - /// - While interpolation is enabled and disabled - /// - [UnityTest] - public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation) - { - // Just test for OverrideState.Update (they are already being tested for functionality in normal NetworkTransformTests) - m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; - m_AuthoritativeTransform.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress; - m_AuthoritativeTransform.UseHalfFloatPrecision = m_Precision == Precision.Half; - m_AuthoritativeTransform.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion; - m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - - - // test position - var authPlayerTransform = m_AuthoritativeTransform.transform; - - Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "server side pos should be zero at first"); // sanity check - - m_AuthoritativeTransform.transform.position = GetRandomVector3(2f, 30f); - - yield return WaitForConditionOrTimeOut(() => PositionsMatch()); - AssertOnTimeout($"Timed out waiting for positions to match {m_AuthoritativeTransform.transform.position} | {m_NonAuthoritativeTransform.transform.position}"); - - // test rotation - Assert.AreEqual(Quaternion.identity, m_NonAuthoritativeTransform.transform.rotation, "wrong initial value for rotation"); // sanity check - - m_AuthoritativeTransform.transform.rotation = Quaternion.Euler(GetRandomVector3(5, 60)); // using euler angles instead of quaternions directly to really see issues users might encounter - - // Make sure the values match - yield return WaitForConditionOrTimeOut(() => RotationsMatch()); - AssertOnTimeout($"Timed out waiting for rotations to match"); - - m_AuthoritativeTransform.StatePushed = false; - m_AuthoritativeTransform.transform.localScale = GetRandomVector3(1, 6); - - // Make sure the scale values match - yield return WaitForConditionOrTimeOut(() => ScaleValuesMatch()); - AssertOnTimeout($"Timed out waiting for scale values to match"); - } - - [UnityTest] - public IEnumerator TestSameFrameDeltaStateAndTeleport([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation) - { - m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - - m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate; - - m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; - - // test position - var authPlayerTransform = m_AuthoritativeTransform.transform; - - Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "server side pos should be zero at first"); // sanity check - - m_AuthoritativeTransform.AuthorityPushedTransformState += OnAuthorityPushedTransformState; - m_RandomPosition = GetRandomVector3(2f, 30f); - m_AuthoritativeTransform.transform.position = m_RandomPosition; - m_Teleported = false; - yield return WaitForConditionOrTimeOut(() => m_Teleported); - AssertOnTimeout($"Timed out waiting for random position to be pushed!"); - - yield return WaitForConditionOrTimeOut(() => PositionsMatch()); - AssertOnTimeout($"Timed out waiting for positions to match {m_AuthoritativeTransform.transform.position} | {m_NonAuthoritativeTransform.transform.position}"); - - var authPosition = m_AuthoritativeTransform.GetSpaceRelativePosition(); - var nonAuthPosition = m_NonAuthoritativeTransform.GetSpaceRelativePosition(); - - var finalPosition = m_TeleportOffset + m_RandomPosition; - Assert.True(Approximately(authPosition, finalPosition), $"Authority did not set its position ({authPosition}) to the teleport position ({finalPosition})!"); - Assert.True(Approximately(nonAuthPosition, finalPosition), $"NonAuthority did not set its position ({nonAuthPosition}) to the teleport position ({finalPosition})!"); - } - - /// - /// For the TestSameFrameDeltaStateAndTeleport test, we want to teleport on the same frame that we had a delta state update when - /// using unreliable delta state updates (i.e. we want the unreliable packet to be sent first and then the teleport to be sent on - /// the next tick. Store off both states when invoked - /// - /// - private void OnAuthorityPushedTransformState(ref NetworkTransform.NetworkTransformState networkTransformState) - { - // Match the first position update - if (Approximately(m_RandomPosition, networkTransformState.GetPosition())) - { - // Teleport to the m_RandomPosition plus the - m_AuthoritativeTransform.SetState(m_TeleportOffset + m_RandomPosition, null, null, false); - m_AuthoritativeTransform.AuthorityPushedTransformState -= OnAuthorityPushedTransformState; - m_Teleported = true; - } - } - } -} -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs.meta deleted file mode 100644 index c363dc6cc9..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformPacketLossTests.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e0f584e8eb891d5459373e96e54fe821 -timeCreated: 1620872927 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 28d1def735..35718a474a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -3939,11 +3939,7 @@ public string NativeHashMapStr(NativeHashMap list) for (var i = 0; i < changed2Removes; ++i) { -#if UTP_TRANSPORT_2_0_ABOVE var which = rand.Next(changed2.Count); -#else - var which = rand.Next(changed2.Count()); -#endif T toRemove = default; foreach (var check in changed2) { @@ -4016,11 +4012,7 @@ public string NativeHashMapStr(NativeHashMap list) for (var i = 0; i < changed2Removes; ++i) { -#if UTP_TRANSPORT_2_0_ABOVE var which = rand.Next(changed2.Count); -#else - var which = rand.Next(changed2.Count()); -#endif TKey toRemove = default; foreach (var check in changed2) { @@ -4037,11 +4029,7 @@ public string NativeHashMapStr(NativeHashMap list) for (var i = 0; i < changed2Changes; ++i) { -#if UTP_TRANSPORT_2_0_ABOVE var which = rand.Next(changed2.Count); -#else - var which = rand.Next(changed2.Count()); -#endif TKey key = default; foreach (var check in changed2) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs index ef8ca268e4..b0ed59dccc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportDriverClient.cs @@ -64,11 +64,7 @@ private void OnDestroy() public void Connect() { -#if UTP_TRANSPORT_2_0_ABOVE var endpoint = NetworkEndpoint.LoopbackIpv4; -#else - var endpoint = NetworkEndPoint.LoopbackIpv4; -#endif endpoint.Port = 7777; m_Connection = m_Driver.Connect(endpoint); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index 711a4f3bef..4cd33fcc9b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -367,63 +367,6 @@ public IEnumerator SendCompletesOnUnreliableSendQueueOverflow() } } -#if !UTP_TRANSPORT_2_0_ABOVE - // Check that simulator parameters are effective. We only check with the drop rate, because - // that's easy to check and we only really want to make sure the simulator parameters are - // configured properly (the simulator pipeline stage is already well-tested in UTP). - [UnityTest] - [UnityPlatform(include = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.WindowsEditor, RuntimePlatform.LinuxEditor })] - public IEnumerator SimulatorParametersAreEffective() - { - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); - - m_Server.SetDebugSimulatorParameters(0, 0, 100); - - m_Server.StartServer(); - m_Client1.StartClient(); - - yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); - - var data = new ArraySegment(new byte[] { 42 }); - m_Client1.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable); - - yield return new WaitForSeconds(MaxNetworkEventWaitTime); - - Assert.AreEqual(1, m_ServerEvents.Count); - - yield return null; - } - - // Check that RTT is reported correctly. - [UnityTest] - [UnityPlatform(include = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.WindowsEditor, RuntimePlatform.LinuxEditor })] - public IEnumerator CurrentRttReportedCorrectly() - { - const int simulatedRtt = 25; - - InitializeTransport(out m_Server, out m_ServerEvents); - InitializeTransport(out m_Client1, out m_Client1Events); - - m_Server.SetDebugSimulatorParameters(simulatedRtt, 0, 0); - - m_Server.StartServer(); - m_Client1.StartClient(); - - yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); - - var data = new ArraySegment(new byte[] { 42 }); - m_Client1.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable); - - yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents, - timeout: MaxNetworkEventWaitTime + (2 * simulatedRtt)); - - Assert.GreaterOrEqual(m_Client1.GetCurrentRtt(m_Client1.ServerClientId), simulatedRtt); - - yield return null; - } -#endif - [UnityTest] public IEnumerator SendQueuesFlushedOnShutdown([ValueSource("k_DeliveryParameters")] NetworkDelivery delivery) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef index 72d85ca4c5..ce8ab1c7eb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/com.unity.netcode.runtimetests.asmdef @@ -48,11 +48,6 @@ "name": "com.unity.modules.physics", "expression": "", "define": "COM_UNITY_MODULES_PHYSICS" - }, - { - "name": "com.unity.transport", - "expression": "2.0.0-exp", - "define": "UTP_TRANSPORT_2_0_ABOVE" } ], "noEngineReferences": false diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs deleted file mode 100644 index e6c5f550bb..0000000000 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs +++ /dev/null @@ -1,325 +0,0 @@ -// TODO: Rewrite test to use the tools package. Debug simulator not available in UTP 2.X. -#if !UTP_TRANSPORT_2_0_ABOVE -using System.Collections; -using System.Collections.Generic; -using NUnit.Framework; -using Unity.Netcode; -using Unity.Netcode.TestHelpers.Runtime; -using Unity.Netcode.Transports.UTP; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.TestTools; -using Object = UnityEngine.Object; - -namespace TestProject.RuntimeTests -{ - /// - /// Use this test group for validating NetworkSceneManager fixes. - /// - public class NetworkSceneManagerFixValidationTests : NetcodeIntegrationTest - { - - private const string k_SceneToLoad = "UnitTestBaseScene"; - private const string k_AdditiveScene1 = "InSceneNetworkObject"; - private const string k_AdditiveScene2 = "AdditiveSceneMultiInstance"; - - protected override int NumberOfClients => 2; - - private bool m_CanStart; - private bool m_NoLatency; - - private Scene m_OriginalActiveScene; - - protected override IEnumerator OnSetup() - { - m_OriginalActiveScene = SceneManager.GetActiveScene(); - return base.OnSetup(); - } - - protected override bool CanStartServerAndClients() - { - return m_CanStart; - } - - /// - /// This validation test verifies that the NetworkSceneManager will not crash if - /// the SpawnManager.SpawnedObjectsList contains destroyed and invalid NetworkObjects. - /// - [Test] - public void DDOLPopulateWithNullNetworkObjectsValidation([Values] bool useHost) - { - var gameObject = new GameObject(); - var networkObject = gameObject.AddComponent(); - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - - m_ServerNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = gameObject }); - - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - clientNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = gameObject }); - } - - // Start the host and clients - if (!NetcodeIntegrationTestHelpers.Start(useHost, m_ServerNetworkManager, new NetworkManager[] { })) - { - Debug.LogError("Failed to start instances"); - Assert.Fail("Failed to start instances"); - } - - // Spawn some NetworkObjects - var spawnedNetworkObjects = new List(); - for (int i = 0; i < 10; i++) - { - var instance = Object.Instantiate(gameObject); - var instanceNetworkObject = instance.GetComponent(); - instanceNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; - instanceNetworkObject.Spawn(); - spawnedNetworkObjects.Add(instance); - } - - // Add a bogus entry to the SpawnManager - m_ServerNetworkManager.SpawnManager.SpawnedObjectsList.Add(null); - - // Verify moving all NetworkObjects into the DDOL when some might be invalid will not crash - m_ServerNetworkManager.SceneManager.MoveObjectsToDontDestroyOnLoad(); - - // Verify moving all NetworkObjects from DDOL back into the active scene will not crash even if some are invalid - m_ServerNetworkManager.SceneManager.MoveObjectsFromDontDestroyOnLoadToScene(SceneManager.GetActiveScene()); - - // Now remove the invalid object - m_ServerNetworkManager.SpawnManager.SpawnedObjectsList.Remove(null); - - // As long as there are no exceptions this test passes - } - - protected override void OnServerAndClientsCreated() - { - if (m_NoLatency) - { - return; - } - - // Apply a 500ms latency on packets (primarily for ClientDisconnectsDuringSeneLoadingValidation) - var serverTransport = m_ServerNetworkManager.GetComponent(); - serverTransport.SetDebugSimulatorParameters(500, 0, 0); - - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - var clientTransport = m_ServerNetworkManager.GetComponent(); - clientTransport.SetDebugSimulatorParameters(500, 0, 0); - } - } - - protected override IEnumerator OnServerAndClientsConnected() - { - m_ServerNetworkManager.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted; - return base.OnServerAndClientsConnected(); - } - - private void SceneManager_OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) - { - if (sceneName == k_SceneToLoad) - { - m_ServerNetworkManager.SceneManager.OnLoadEventCompleted -= SceneManager_OnLoadEventCompleted; - m_SceneLoadEventCompleted = true; - - // Verify that the disconnected client is in the clients that timed out list - Assert.IsTrue(clientsTimedOut.Contains(m_FirstClientId), $"Client-id({m_FirstClientId}) was not found in the clients that timed out list that has a count of ({clientsTimedOut.Count}) entries!"); - } - } - - private bool m_SceneLoadEventCompleted; - private bool m_ClientDisconnectedOnLoad; - private ulong m_FirstClientId; - - [UnityTest] - public IEnumerator ClientDisconnectsDuringSeneLoadingValidation() - { - m_CanStart = true; - yield return StartServerAndClients(); - - // Do some preparation for the client we will be disconnecting. - m_FirstClientId = m_ClientNetworkManagers[0].LocalClientId; - if (m_ClientNetworkManagers[0].SceneManager.VerifySceneBeforeLoading != null) - { - m_OriginalVerifyScene = m_ClientNetworkManagers[0].SceneManager.VerifySceneBeforeLoading; - } - m_ClientNetworkManagers[0].SceneManager.VerifySceneBeforeLoading = VerifySceneBeforeLoading; - - // We use this to verify that the loading scene event doesn't take NetworkConfig.LoadSceneTimeOut to complete - var timeEventStarted = Time.realtimeSinceStartup; - var disconnectedClientName = m_ClientNetworkManagers[0].name; - - // Start to load the scene - m_ServerNetworkManager.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); - yield return WaitForConditionOrTimeOut(() => m_SceneLoadEventCompleted); - AssertOnTimeout("Timed out waiting for the load scene event to be completed!"); - - // Verify the client disconnected when it was just about to start the load event - Assert.True(m_ClientDisconnectedOnLoad, $"{disconnectedClientName} did not disconnect!"); - - // Verify it didn't take as long as the NetworkConfig.LoadSceneTimeOut period to complete the event - var timeToComplete = Time.realtimeSinceStartup - timeEventStarted; - Assert.True(timeToComplete < m_ServerNetworkManager.NetworkConfig.LoadSceneTimeOut, "Server scene loading event timed out!"); - - // Verification that the disconnected client was in the timeout list is done in SceneManager_OnLoadEventCompleted - } - - private NetworkSceneManager.VerifySceneBeforeLoadingDelegateHandler m_OriginalVerifyScene; - - /// - /// The client that disconnects during the scene event will have this override that will: - /// - Set m_ClientDisconnectedOnLoad - /// - Stop/Disconnect the client - /// - Return false (i.e. don't load this scene) as we are simulating the client disconnected - /// right as the scene event started - /// - private bool VerifySceneBeforeLoading(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) - { - if (sceneName == k_SceneToLoad) - { - m_ClientDisconnectedOnLoad = true; - NetcodeIntegrationTestHelpers.StopOneClient(m_ClientNetworkManagers[0]); - IntegrationTestSceneHandler.NetworkManagers.Remove(m_ClientNetworkManagers[0]); - return false; - } - if (m_OriginalVerifyScene != null) - { - return m_OriginalVerifyScene.Invoke(sceneIndex, sceneName, loadSceneMode); - } - return true; - } - - - private Scene m_FirstScene; - private Scene m_SecondScene; - private Scene m_ThirdScene; - - private bool m_SceneUnloadedEventCompleted; - - - [UnityTest] - public IEnumerator InitialActiveSceneUnload() - { - SceneManager.sceneLoaded += SceneManager_sceneLoaded; - SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); - - yield return WaitForConditionOrTimeOut(FirstSceneIsLoaded); - AssertOnTimeout($"Failed to load scene {k_SceneToLoad}!"); - - // Now set the "first scene" as the active scene prior to starting the server and clients - SceneManager.SetActiveScene(m_FirstScene); - m_NoLatency = true; - m_CanStart = true; - - yield return StartServerAndClients(); - - var serverSceneManager = m_ServerNetworkManager.SceneManager; - serverSceneManager.OnSceneEvent += ServerSceneManager_OnSceneEvent; - m_SceneLoadEventCompleted = false; - serverSceneManager.LoadScene(k_AdditiveScene1, LoadSceneMode.Additive); - - yield return WaitForConditionOrTimeOut(SecondSceneIsLoaded); - AssertOnTimeout($"[Load Event] Failure in loading scene {k_AdditiveScene1} (locally or on client side)!"); - - // Since we have to keep the test running scene active, we mimic the "auto assignment" of - // the active scene prior to unloading the first scene in order to validate this test scenario. - SceneManager.SetActiveScene(m_SecondScene); - - yield return s_DefaultWaitForTick; - - // Now unload the "first" scene which, if this was the only scene loaded prior to loading the second scene, - // would automatically make the second scene the currently active scene - m_SceneUnloadedEventCompleted = false; - serverSceneManager.UnloadScene(m_FirstScene); - yield return WaitForConditionOrTimeOut(SceneUnloadEventCompleted); - AssertOnTimeout($"[Unload Event] Failure in unloading scene {m_FirstScene} (locally or on client side)!"); - - // Now load the third scene, and if no time out occurs then we have validated this test! - m_SceneLoadEventCompleted = false; - serverSceneManager.LoadScene(k_AdditiveScene2, LoadSceneMode.Additive); - yield return WaitForConditionOrTimeOut(ThirdSceneIsLoaded); - AssertOnTimeout($"[Load Event] Failure in loading scene {k_AdditiveScene2} (locally or on client side)!"); - serverSceneManager.OnSceneEvent -= ServerSceneManager_OnSceneEvent; - } - - private void ServerSceneManager_OnSceneEvent(SceneEvent sceneEvent) - { - if (sceneEvent.ClientId != m_ServerNetworkManager.LocalClientId) - { - return; - } - - if (sceneEvent.SceneEventType == SceneEventType.LoadComplete) - { - if (sceneEvent.Scene.name == k_AdditiveScene1) - { - m_SecondScene = sceneEvent.Scene; - } - else if (sceneEvent.Scene.name == k_AdditiveScene2) - { - m_ThirdScene = sceneEvent.Scene; - } - } - - if (sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted) - { - if (sceneEvent.SceneName == k_AdditiveScene1 || sceneEvent.SceneName == k_AdditiveScene2) - { - m_SceneLoadEventCompleted = true; - } - } - - if (sceneEvent.SceneEventType == SceneEventType.UnloadEventCompleted) - { - if (sceneEvent.SceneName == k_SceneToLoad || sceneEvent.SceneName == k_AdditiveScene1 || sceneEvent.SceneName == k_AdditiveScene2) - { - m_SceneUnloadedEventCompleted = true; - } - } - } - - private bool SceneUnloadEventCompleted() - { - return m_SceneUnloadedEventCompleted; - } - - private bool FirstSceneIsLoaded() - { - return m_FirstScene.IsValid() && m_FirstScene.isLoaded; - } - - private bool SecondSceneIsLoaded() - { - return m_SecondScene.IsValid() && m_SecondScene.isLoaded && m_SceneLoadEventCompleted; - } - - private bool ThirdSceneIsLoaded() - { - return m_ThirdScene.IsValid() && m_ThirdScene.isLoaded && m_SceneLoadEventCompleted; - } - - private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode mode) - { - if (scene.name == k_SceneToLoad && mode == LoadSceneMode.Additive) - { - m_FirstScene = scene; - SceneManager.sceneLoaded -= SceneManager_sceneLoaded; - } - } - - protected override IEnumerator OnTearDown() - { - m_CanStart = false; - m_NoLatency = false; - - if (m_OriginalActiveScene != SceneManager.GetActiveScene()) - { - SceneManager.SetActiveScene(m_OriginalActiveScene); - } - - return base.OnTearDown(); - } - } -} -#endif diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs.meta deleted file mode 100644 index 9e35df982a..0000000000 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerFixValidationTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 501dfd0e05e51184e8938bba980d3f88 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef index 80fb79d2d3..17af49a925 100644 --- a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef +++ b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef @@ -37,11 +37,6 @@ "name": "com.unity.addressables", "expression": "", "define": "TESTPROJECT_USE_ADDRESSABLES" - }, - { - "name": "com.unity.transport", - "expression": "2.0.0-exp", - "define": "UTP_TRANSPORT_2_0_ABOVE" } ], "noEngineReferences": false From 120cdcca28657e6bcc1ae1c686d0295d8c67b491 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 28 Mar 2025 17:59:15 -0500 Subject: [PATCH 209/236] fix: OnGainedOwnership and OnLostOwnership XML API updates (#3365) Correcting the XML API documentation for OnGainedOwnership and OnLostOwnership when using a distributed authority network topology. [MTTB-1086](https://jira.unity3d.com/browse/MTTB-1086) fix: #3345 ## Changelog NA ## Testing and Documentation - No tests have been added. - Includes edits to existing public API documentation. --- .../Runtime/Core/NetworkBehaviour.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 29cbff8d2e..1f98d0b840 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -826,7 +826,7 @@ internal void InternalOnNetworkDespawn() /// /// In client-server contexts, this method is invoked on both the server and the local client of the owner when ownership is assigned. - /// In distributed authority contexts, this method is only invoked on the local client that has been assigned ownership of the associated . + /// In distributed authority contexts, this method is invoked on all clients connected to the session. /// public virtual void OnGainedOwnership() { } @@ -863,7 +863,7 @@ internal void InternalOnOwnershipChanged(ulong previous, ulong current) /// /// In client-server contexts, this method is invoked on the local client when it loses ownership of the associated /// and on the server when any client loses ownership. - /// In distributed authority contexts, this method is only invoked on the local client that has lost ownership of the associated . + /// In distributed authority contexts, this method is invoked on all clients connected to the session. /// public virtual void OnLostOwnership() { } From dfef393af0e6c2197ebb69bc6c4c1566060b49b0 Mon Sep 17 00:00:00 2001 From: jonathanhertz <59826545+jonathanhertz@users.noreply.github.com> Date: Sat, 29 Mar 2025 03:30:55 +0100 Subject: [PATCH 210/236] fix: [NGOv2.x] Add OnPreShutdown to NetworkManager (#3366) Forwardport of https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3358 This PR adds a `NetworkManager.OnPreShutdown` callback that happens before the internal shutdown is done. This is to allow accessing any state that is cleaned up by the `NetworkManager` during shutdown, such as accessing dynamically spawned NetworkObjects. ## Changelog - Added: The event NetworkManager.OnPreShutdown has been added which is called before the NetworkManager cleans up and shuts down. ## Testing and Documentation - A test has been added to ensure this is being called, and called before OnServerStopped. --------- Co-authored-by: Noel Stephens --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Core/NetworkManager.cs | 8 ++++ .../Runtime/NetworkManagerEventsTests.cs | 40 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9996ef2d61..73ed64a735 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `NetworkManager.OnPreShutdown` which is called before the NetworkManager cleans up and shuts down. (#3366) - Added interpolator types as an inspector view selection for position, rotation, and scale. (#3337) - Added a new smooth dampening interpolator type that provides a nice balance between precision and smoothing results. (#3337) - Added `NetworkTimeSystem.TickLatency` property that provides the average latency of a client. (#3337) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index d564f0f8f3..a35fcfec75 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -807,6 +807,12 @@ public struct ConnectionApprovalRequest /// public event Action OnClientStarted = null; + /// + /// Subscribe to this event to get notifications before a instance is being destroyed. + /// This is useful if you want to use the state of anything the NetworkManager cleans up during its shutdown. + /// + public event Action OnPreShutdown = null; + /// /// This callback is invoked once the local server is stopped. /// @@ -1481,6 +1487,8 @@ internal void ShutdownInternal() NetworkLog.LogInfo(nameof(ShutdownInternal)); } + OnPreShutdown?.Invoke(); + this.UnregisterAllNetworkUpdates(); // Everything is shutdown in the order of their dependencies diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs index 8b1c993268..c2d482da67 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerEventsTests.cs @@ -236,6 +236,46 @@ public IEnumerator OnClientAndServerStartedCalledWhenHostStarts() Assert.AreEqual(2, callbacksInvoked, "either OnServerStarted or OnClientStarted wasn't invoked"); } + [UnityTest] + public IEnumerator OnPreShutdownCalledWhenShuttingDown() + { + bool preShutdownInvoked = false; + bool shutdownInvoked = false; + var gameObject = new GameObject(nameof(OnPreShutdownCalledWhenShuttingDown)); + m_ServerManager = gameObject.AddComponent(); + + // Set dummy transport that does nothing + var transport = gameObject.AddComponent(); + m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; + + Action onPreShutdown = () => + { + preShutdownInvoked = true; + Assert.IsFalse(shutdownInvoked, "OnPreShutdown was invoked after OnServerStopped"); + }; + + Action onServerStopped = (bool wasAlsoClient) => + { + shutdownInvoked = true; + Assert.IsTrue(preShutdownInvoked, "OnPreShutdown wasn't invoked before OnServerStopped"); + }; + + // Start server to cause initialization process + Assert.True(m_ServerManager.StartServer()); + Assert.True(m_ServerManager.IsListening); + + m_ServerManager.OnPreShutdown += onPreShutdown; + m_ServerManager.OnServerStopped += onServerStopped; + m_ServerManager.Shutdown(); + Object.DestroyImmediate(gameObject); + + yield return WaitUntilManagerShutsdown(); + + Assert.False(m_ServerManager.IsListening); + Assert.True(preShutdownInvoked, "OnPreShutdown wasn't invoked"); + Assert.True(shutdownInvoked, "OnServerStopped wasn't invoked"); + } + private IEnumerator WaitUntilManagerShutsdown() { /* Need two updates to actually shut down. First one to see the transport failing, which From 2d361613f7e531ab002b3685debf488a94d5be8a Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 13:30:06 +0200 Subject: [PATCH 211/236] chore(deps): update dependency recipeengine.modules.wrench to 0.10.45 (develop-2.0.0) (#3360) Wrench version update to 0.10.45 and recipes regeneration --- .yamato/wrench/api-validation-jobs.yml | 6 +-- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 56 +++++++++++++------------- .yamato/wrench/promotion-jobs.yml | 48 +++++++++++++++++++--- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 54 ++++++++++++------------- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- 8 files changed, 104 insertions(+), 68 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 5167bb6727..7a6c445e71 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -13,7 +13,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -51,8 +51,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index fd763f6fa5..6413921ce7 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -24,5 +24,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index a6463fe189..aa9d8cc1d5 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,7 +16,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_2_-_windows metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos: @@ -26,7 +26,7 @@ preview_apv_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -67,10 +67,10 @@ preview_apv_-_6000_0_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu: @@ -80,7 +80,7 @@ preview_apv_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -121,10 +121,10 @@ preview_apv_-_6000_0_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_windows: @@ -135,7 +135,7 @@ preview_apv_-_6000_0_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -176,10 +176,10 @@ preview_apv_-_6000_0_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.1 manifest (MacOS). preview_apv_-_6000_1_-_macos: @@ -189,7 +189,7 @@ preview_apv_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -230,10 +230,10 @@ preview_apv_-_6000_1_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.1 manifest (Ubuntu). preview_apv_-_6000_1_-_ubuntu: @@ -243,7 +243,7 @@ preview_apv_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -284,10 +284,10 @@ preview_apv_-_6000_1_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.1 manifest (Windows). preview_apv_-_6000_1_-_windows: @@ -298,7 +298,7 @@ preview_apv_-_6000_1_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -339,10 +339,10 @@ preview_apv_-_6000_1_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.2 manifest (MacOS). preview_apv_-_6000_2_-_macos: @@ -352,7 +352,7 @@ preview_apv_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -393,10 +393,10 @@ preview_apv_-_6000_2_-_macos: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.2 manifest (Ubuntu). preview_apv_-_6000_2_-_ubuntu: @@ -406,7 +406,7 @@ preview_apv_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -447,10 +447,10 @@ preview_apv_-_6000_2_-_ubuntu: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Functional tests for dependents found in the latest 6000.2 manifest (Windows). preview_apv_-_6000_2_-_windows: @@ -461,7 +461,7 @@ preview_apv_-_6000_2_-_windows: flavor: b1.large commands: - command: gsudo reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -502,8 +502,8 @@ preview_apv_-_6000_2_-_windows: dependencies: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 4bef3a5a45..ddd5e5322f 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -9,7 +9,7 @@ publish_dry_run_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -34,6 +34,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.0-macos unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_ubuntu specific_options: UTR: @@ -42,6 +44,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.0-ubuntu unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_windows specific_options: UTR: @@ -50,6 +54,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.0-windows unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_macos specific_options: UTR: @@ -58,6 +64,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-macos unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_ubuntu specific_options: UTR: @@ -66,6 +74,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-ubuntu unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_windows specific_options: UTR: @@ -74,6 +84,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-windows unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_macos specific_options: UTR: @@ -82,6 +94,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.2-macos unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_ubuntu specific_options: UTR: @@ -90,6 +104,8 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.2-ubuntu unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_windows specific_options: UTR: @@ -98,12 +114,14 @@ publish_dry_run_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.2-windows unzip: true + packages: + ignore_artifact: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -113,7 +131,7 @@ publish_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -138,6 +156,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.0-macos unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_ubuntu specific_options: UTR: @@ -146,6 +166,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.0-ubuntu unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_0_-_windows specific_options: UTR: @@ -154,6 +176,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.0-windows unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_macos specific_options: UTR: @@ -162,6 +186,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-macos unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_ubuntu specific_options: UTR: @@ -170,6 +196,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-ubuntu unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_1_-_windows specific_options: UTR: @@ -178,6 +206,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.1-windows unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_macos specific_options: UTR: @@ -186,6 +216,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.2-macos unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_ubuntu specific_options: UTR: @@ -194,6 +226,8 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.2-ubuntu unzip: true + packages: + ignore_artifact: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_2_-_windows specific_options: UTR: @@ -202,10 +236,12 @@ publish_netcode_gameobjects: pvp-results: location: results/pvp/validate-netcode.gameobjects-6000.2-windows unzip: true + packages: + ignore_artifact: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index c2b89eecc2..7cec6a1ea8 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -26,5 +26,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 7a5b3710e0..968b3da71d 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -9,7 +9,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -60,10 +60,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -75,7 +75,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -126,10 +126,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -141,7 +141,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -192,10 +192,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -207,7 +207,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -258,10 +258,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -273,7 +273,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -324,10 +324,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -339,7 +339,7 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -390,10 +390,10 @@ validate_-_netcode_gameobjects_-_6000_1_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -405,7 +405,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -456,10 +456,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_macos: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -471,7 +471,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -522,10 +522,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_ubuntu: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects @@ -537,7 +537,7 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-59_594aea2e5eb3a7468ad88458b5c5da2e5e72af0d2267db7b025602fb69e57bd7.zip -o wrench-localapv.zip + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-2-60_641f75ebc79901fc7eb7880e924d4adc41d99ad42d02aa320814a9e1e75463c4.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip - command: pip install semver requests --index-url https://artifactory-slo.bf.unity3d.com/artifactory/api/pypi/pypi/simple - command: python PythonScripts/print_machine_info.py @@ -588,10 +588,10 @@ validate_-_netcode_gameobjects_-_6000_2_-_windows: - path: .yamato/wrench/package-pack-jobs.yml#package_pack_-_netcode_gameobjects variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 0.10.44.0 + UPMPVP_CONTEXT_WRENCH: 0.10.45.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 0.10.44.0 + Wrench: 0.10.45.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 3e50e220b0..b25762e36c 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -31,7 +31,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "0.10.44.0", + "wrench_version": "0.10.45.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools\\CI\\NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index d994233e00..968e57e6a2 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,7 +8,7 @@ - + From 7d4801ff4111872bb4b43c177fbdfcd2d3d545e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:44:54 +0200 Subject: [PATCH 212/236] ci: [2.X] disabling WebGL Build job on macOS due to "Light baking could not be started because no valid OpenCL device could be found" error. (#3369) After switching from Intel macOS to Apple Silicon due to the common problem with bit-flipping on Intel macOS the WebGL build job started failing due to "Light baking could not be started because no valid OpenCL device could be found" error. We need to figure out if this can be fixed or how to proceed with it. * PR that switched macOS device --> [NGOv1.X](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3326), [NGOv2.X](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3329) The issue exists on both NGO branches (*develop* and *develop-2.0.0*) so the solution needs to be applied on both. For now I'm disabling this job and tracking it in MTT-11726 --- .yamato/_run-all.yml | 4 ++++ .yamato/webgl-build.yml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.yamato/_run-all.yml b/.yamato/_run-all.yml index 985e1d2f30..ec26e6eb90 100644 --- a/.yamato/_run-all.yml +++ b/.yamato/_run-all.yml @@ -81,9 +81,11 @@ run_all_webgl_builds: dependencies: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} +{% if platform.name != "mac" -%} # There is an error about "Light baking could not be started because no valid OpenCL device could be found". Tracked in MTT-11726 {% for editor in validation_editors.all -%} - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} +{% endif -%} {% endfor -%} {% endfor -%} @@ -94,9 +96,11 @@ run_all_webgl_builds_trunk: dependencies: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} +{% if platform.name != "mac" -%} # There is an error about "Light baking could not be started because no valid OpenCL device could be found". Tracked in MTT-11726 {% for editor in validation_editors.default -%} - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }} {% endfor -%} +{% endif -%} {% endfor -%} {% endfor -%} diff --git a/.yamato/webgl-build.yml b/.yamato/webgl-build.yml index 393998f407..7b9756a3bf 100644 --- a/.yamato/webgl-build.yml +++ b/.yamato/webgl-build.yml @@ -25,6 +25,7 @@ {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} +{% if platform.name != "mac" -%} # There is an error about "Light baking could not be started because no valid OpenCL device could be found". Tracked in MTT-11726 {% for editor in validation_editors.all -%} webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: name: WebGl Build - {{ project.name }} [{{ platform.name }}, {{ editor }}, il2cpp] @@ -51,5 +52,6 @@ webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: - "artifacts/**/*" - "build/players/**/*" {% endfor -%} +{% endif -%} {% endfor -%} {% endfor -%} From 464e7cb39782f0b2cb0b102f8842f8d88663e5e7 Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 31 Mar 2025 17:14:09 -0400 Subject: [PATCH 213/236] chore: [2.x] Add NetworkTransform parenting test (#3368) 1. Adds a test replicating the bug fixed in #3361 2. Move the code to sync prefabs for late joining clients into the base `NetcodeIntegrationTest` 3. Fix some pvp exceptions ## Changelog - Added: Test covering the bug fixed in #3361 ## Testing and Documentation - Adds Unit test --------- Co-authored-by: Noel Stephens --- .../Runtime/NetcodeIntegrationTest.cs | 14 ++ .../Tests/Runtime/DeferredMessagingTests.cs | 3 +- .../DistributeObjectsTests.cs | 6 - .../ExtendedNetworkShowAndHideTests.cs | 1 - .../NetworkClientAndPlayerObjectTests.cs | 1 - .../NetworkBehaviourPrePostSpawnTests.cs | 6 - .../NetworkObjectOnSpawnTests.cs | 7 - .../NetworkObjectSynchronizationTests.cs | 15 +- .../NetworkTransform/NetworkTransformBase.cs | 1 - .../NetworkTransformOwnershipTests.cs | 14 -- .../NetworkTransformParentingTests.cs | 183 ++++++++++++++++++ .../NetworkTransformParentingTests.cs.meta | 3 + pvpExceptions.json | 6 - .../Runtime/Animation/NetworkAnimatorTests.cs | 12 -- .../Tests/Runtime/DontDestroyOnLoadTests.cs | 6 - .../Tests/Runtime/NetworkManagerTests.cs | 8 +- .../NetworkObjectSceneMigrationTests.cs | 13 -- .../ParentDynamicUnderInScenePlaced.cs | 9 - .../ParentingWorldPositionStaysTests.cs | 8 - .../Tests/Runtime/PrefabExtendedTests.cs | 9 +- 20 files changed, 212 insertions(+), 113 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs.meta diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 95bf441e4d..a6f0f3a4cb 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -466,14 +466,24 @@ private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetw /// CreateAndStartNewClient Only /// Invoked when the newly created client has been created /// + /// The NetworkManager instance of the client. protected virtual void OnNewClientCreated(NetworkManager networkManager) { + // Ensure any late joining client has all NetworkPrefabs required to connect. + foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) + { + if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab)) + { + networkManager.NetworkConfig.Prefabs.Add(networkPrefab); + } + } } /// /// CreateAndStartNewClient Only /// Invoked when the newly created client has been created and started /// + /// The NetworkManager instance of the client. protected virtual void OnNewClientStarted(NetworkManager networkManager) { } @@ -483,6 +493,7 @@ protected virtual void OnNewClientStarted(NetworkManager networkManager) /// Invoked when the newly created client has been created, started, and connected /// to the server-host. ///
+ /// The NetworkManager instance of the client. protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager) { } @@ -494,6 +505,8 @@ protected virtual void OnNewClientStartedAndConnected(NetworkManager networkMana /// /// Use this for testing connection and disconnection scenarios /// + /// The NetworkManager instance of the client. + /// True if the test should wait for the client to connect; otherwise, false. protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkManager) { return true; @@ -503,6 +516,7 @@ protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkMan /// This will create, start, and connect a new client while in the middle of an /// integration test. ///
+ /// An IEnumerator to be used in a coroutine for asynchronous execution. protected IEnumerator CreateAndStartNewClient() { var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index 02cab89b5e..5c2b0ca1d4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -259,8 +259,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager) { AddPrefabsToClient(networkManager); } - - base.OnNewClientCreated(networkManager); + // Don't call base to avoid synchronizing the prefabs } private void SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs index 7957e6cf0c..69680d96f2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributeObjectsTests.cs @@ -235,12 +235,6 @@ private bool ValidateTransformsMatch() return true; } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; - base.OnNewClientCreated(networkManager); - } - private bool SpawnCountsMatch() { var passed = true; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs index 1ebb0f0411..94cdd8b02b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/ExtendedNetworkShowAndHideTests.cs @@ -97,7 +97,6 @@ protected override void OnNewClientCreated(NetworkManager networkManager) { m_LateJoinClient = networkManager; networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; - networkManager.NetworkConfig.Prefabs = m_SpawnOwner.NetworkConfig.Prefabs; base.OnNewClientCreated(networkManager); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index 699b82d636..d768f60939 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -45,7 +45,6 @@ protected override void OnServerAndClientsCreated() protected override void OnNewClientCreated(NetworkManager networkManager) { - networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; if (m_DistributedAuthority) { networkManager.OnFetchLocalPlayerPrefabToSpawn = FetchPlayerPrefabToSpawn; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs index 00d3c08613..fc233a8653 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs @@ -93,12 +93,6 @@ protected override IEnumerator OnSetup() return base.OnSetup(); } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; - base.OnNewClientCreated(networkManager); - } - /// /// This validates that pre spawn can be used to instantiate and assign a NetworkVariable (or other prespawn tasks) /// which can be useful for assigning a NetworkVariable value on the server side when the NetworkVariable has owner write permissions. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 841bad4dc7..8d8f02311f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -86,13 +86,6 @@ private bool CheckClientsSideObserverTestObj() /// protected override void OnNewClientCreated(NetworkManager networkManager) { - foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) - { - if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab)) - { - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - } - } networkManager.NetworkConfig.EnableSceneManagement = m_ServerNetworkManager.NetworkConfig.EnableSceneManagement; base.OnNewClientCreated(networkManager); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index c4714d7fe3..fc59ad09dc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -81,21 +81,16 @@ protected override void OnServerAndClientsCreated() protected override void OnNewClientCreated(NetworkManager networkManager) { + // Setup late joining client prefabs first + base.OnNewClientCreated(networkManager); + networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety; - foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) - { - // To simulate a failure, we exclude the m_InValidNetworkPrefab from the connecting - // client's side. - if (networkPrefab.Prefab.name != m_InValidNetworkPrefab.name) - { - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - } - } // Disable forcing the same prefabs to avoid failed connections networkManager.NetworkConfig.ForceSamePrefabs = false; networkManager.LogLevel = m_CurrentLogLevel; - base.OnNewClientCreated(networkManager); + // To simulate a failure, exclude the m_InValidNetworkPrefab from the connecting client's side. + networkManager.NetworkConfig.Prefabs.Remove(m_InValidNetworkPrefab); } [UnityTest] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 4413b73fee..703ed7433e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -338,7 +338,6 @@ protected override IEnumerator OnServerAndClientsConnected() ///
protected override void OnNewClientCreated(NetworkManager networkManager) { - networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; networkManager.NetworkConfig.TickRate = GetTickRate(); if (m_EnableVerboseDebug) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs index 229a067758..64f6646005 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs @@ -91,20 +91,6 @@ protected override void OnServerAndClientsCreated() base.OnServerAndClientsCreated(); } - /// - /// Clients created during a test need to have their prefabs list updated to - /// match the server's prefab list. - /// - protected override void OnNewClientCreated(NetworkManager networkManager) - { - foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) - { - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - } - - base.OnNewClientCreated(networkManager); - } - private bool ClientIsOwner() { var clientId = m_ClientNetworkManagers[0].LocalClientId; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs new file mode 100644 index 0000000000..896e7ddda2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs @@ -0,0 +1,183 @@ +using System.Collections; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + internal class NetworkTransformParentingTests : IntegrationTestWithApproximation + { + /// + /// A NetworkBehaviour that moves in space. + /// When spawned on the client, an RPC is sent to the server to spawn a player object for that client. + /// The server parents the player object to the spawner object. This gives a moving parent object and a non-moving child object. + /// The child object should always be at {0,0,0} local space, while the parent object moves around. + /// This NetworkBehaviour tests that parenting to a moving object works as expected. + /// + internal class PlayerSpawner : NetworkBehaviour + { + /// + /// Prefab for the player + /// + public NetworkObject PlayerPrefab; + + /// + /// The server side NetworkObject that was spawned when the client connected. + /// + public NetworkObject SpawnedPlayer; + + /// + /// Represents the different movement states of the PlayerSpawner during the test lifecycle. + /// + public enum MoveState + { + // Initial state, PlayerSpawner will move without counting frames + NotStarted, + // The player object has been spawned, start counting frames + PlayerSpawned, + // We have moved far enough to test location + ReachedPeak, + } + public MoveState State = MoveState.NotStarted; + + // A count of the number of updates since the player object was spawned. + private int m_Count; + + // Movement offsets and targets. + private const float k_PositionOffset = 5.0f; + private const float k_RotationOffset = 25.0f; + private readonly Vector3 m_PositionTarget = Vector3.one * k_PositionOffset * 10; + private readonly Vector3 m_RotationTarget = Vector3.one * k_RotationOffset * 10; + + private void Update() + { + if (!IsServer) + { + return; + } + + transform.position = Vector3.Lerp(transform.position, m_PositionTarget, Time.deltaTime * 2); + var rotation = transform.rotation; + rotation.eulerAngles = Vector3.Slerp(rotation.eulerAngles, m_RotationTarget, Time.deltaTime * 2); + transform.rotation = rotation; + + if (State != MoveState.PlayerSpawned) + { + return; + } + + // Move self for some time after player object is spawned + // This ensures the parent object is moving throughout the spawn process. + m_Count++; + if (m_Count > 10) + { + // Mark PlayerSpawner as having moved far enough to test. + State = MoveState.ReachedPeak; + } + } + + public override void OnNetworkSpawn() + { + if (IsOwner) + { + // Owner initialises PlayerSpawner movement on spawn + transform.position = Vector3.one * k_PositionOffset; + var rotation = transform.rotation; + rotation.eulerAngles = Vector3.one * k_RotationOffset; + transform.rotation = rotation; + } + else + { + // When spawned on a client, send the RPC to spawn the player object + // Using an RPC ensures the PlayerSpawner is moving for the entire spawning of the player object. + RequestPlayerObjectSpawnServerRpc(); + } + } + + /// + /// A ServerRpc that requests the server to spawn a player object for the client that invoked this RPC. + /// + /// Parameters for the ServerRpc, including the sender's client ID. + [ServerRpc(RequireOwnership = false)] + private void RequestPlayerObjectSpawnServerRpc(ServerRpcParams rpcParams = default) + { + SpawnedPlayer = Instantiate(PlayerPrefab); + SpawnedPlayer.SpawnAsPlayerObject(rpcParams.Receive.SenderClientId); + SpawnedPlayer.TrySetParent(NetworkObject, false); + State = MoveState.PlayerSpawned; + } + } + + // Don't start with any clients, we will manually spawn a client inside the test + protected override int NumberOfClients => 0; + + // Parent prefab with moving PlayerSpawner which will spawn the childPrefab + private GameObject m_PlayerSpawnerPrefab; + + // Client and server instances + private PlayerSpawner m_ServerPlayerSpawner; + private NetworkObject m_NewClientPlayer; + + protected override void OnServerAndClientsCreated() + { + m_PlayerSpawnerPrefab = CreateNetworkObjectPrefab("Parent"); + var parentPlayerSpawner = m_PlayerSpawnerPrefab.AddComponent(); + m_PlayerSpawnerPrefab.AddComponent(); + + var playerPrefab = CreateNetworkObjectPrefab("Child"); + var childNetworkTransform = playerPrefab.AddComponent(); + childNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + childNetworkTransform.InLocalSpace = true; + + parentPlayerSpawner.PlayerPrefab = playerPrefab.GetComponent(); + + base.OnServerAndClientsCreated(); + } + + private bool NewPlayerObjectSpawned() + { + return m_ServerPlayerSpawner.SpawnedPlayer && + m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(m_ServerPlayerSpawner.SpawnedPlayer.NetworkObjectId); + } + + private bool HasServerInstanceReachedPeakPoint() + { + VerboseDebug($"Client Local: {m_NewClientPlayer.transform.localPosition} Server Local: {m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition}"); + return m_ServerPlayerSpawner.State == PlayerSpawner.MoveState.ReachedPeak; + } + + private bool ServerClientPositionMatches() + { + return Approximately(m_NewClientPlayer.transform.localPosition, m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition) && + Approximately(m_NewClientPlayer.transform.position, m_ServerPlayerSpawner.SpawnedPlayer.transform.position); + } + + [UnityTest] + public IEnumerator TestParentedPlayerUsingLocalSpace() + { + // Spawn the PlayerSpawner object and save the instantiated component + // The PlayerSpawner object will start moving. + m_ServerPlayerSpawner = SpawnObject(m_PlayerSpawnerPrefab, m_ServerNetworkManager).GetComponent(); + + // Create a new client and connect to the server + // The client will prompt the server to spawn a player object and parent it to the PlayerSpawner object. + yield return CreateAndStartNewClient(); + + yield return WaitForConditionOrTimeOut(NewPlayerObjectSpawned); + AssertOnTimeout($"Client did not spawn new player object!"); + + // Save the spawned player object + m_NewClientPlayer = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[m_ServerPlayerSpawner.SpawnedPlayer.NetworkObjectId]; + + // Let the parent PlayerSpawner move for several ticks to get an offset + yield return WaitForConditionOrTimeOut(HasServerInstanceReachedPeakPoint); + AssertOnTimeout($"Server instance never reached peak point!"); + + // Check that the client and server local positions match (they should both be at {0,0,0} local space) + yield return WaitForConditionOrTimeOut(ServerClientPositionMatches); + AssertOnTimeout($"Client local position {m_NewClientPlayer.transform.localPosition} does not match" + + $" server local position {m_ServerPlayerSpawner.SpawnedPlayer.transform.localPosition}"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs.meta new file mode 100644 index 0000000000..d3c09da900 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 26e2041cdadd44419941135a150b75bd +timeCreated: 1743196812 \ No newline at end of file diff --git a/pvpExceptions.json b/pvpExceptions.json index e717d6626f..23263680db 100644 --- a/pvpExceptions.json +++ b/pvpExceptions.json @@ -238,12 +238,6 @@ "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OneTimeSetup(): undocumented", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator OnSetup(): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator SetUp(): undocumented", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientCreated(NetworkManager): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStarted(NetworkManager): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: void OnNewClientStartedAndConnected(NetworkManager): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: bool ShouldWaitForNewClientToConnect(NetworkManager): missing ", - "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator CreateAndStartNewClient(): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing ", "Unity.Netcode.TestHelpers.Runtime.NetcodeIntegrationTest: IEnumerator StopOneClient(NetworkManager, bool): missing ", diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index fe64a56431..7befcfab22 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -624,18 +624,6 @@ public IEnumerator TriggerUpdateTests() VerboseDebug($" ------------------ Trigger Test [{TriggerTest.Iteration}][{m_OwnerShipMode}] Stopping ------------------ "); } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - var networkPrefab = new NetworkPrefab() { Prefab = m_AnimationTestPrefab }; - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - networkPrefab = new NetworkPrefab() { Prefab = m_AnimationOwnerTestPrefab }; - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - networkPrefab = new NetworkPrefab() { Prefab = m_AnimationCheerTestPrefab }; - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - networkPrefab = new NetworkPrefab() { Prefab = m_AnimationCheerOwnerTestPrefab }; - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - } - /// /// Verifies that triggers are synchronized with currently connected clients /// diff --git a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs index f3794d46b5..dcea70efdd 100644 --- a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs +++ b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs @@ -22,12 +22,6 @@ protected override void OnServerAndClientsCreated() base.OnServerAndClientsCreated(); } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs; - base.OnNewClientCreated(networkManager); - } - private ulong m_SpawnedNetworkObjectId; private StringBuilder m_ErrorLog = new StringBuilder(); diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs index b09dd5e291..5756f81b85 100644 --- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs @@ -108,16 +108,12 @@ public enum ShutdownChecks protected override void OnNewClientCreated(NetworkManager networkManager) { networkManager.NetworkConfig.EnableSceneManagement = m_UseSceneManagement; - foreach (var prefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) - { - networkManager.NetworkConfig.Prefabs.Add(prefab); - } base.OnNewClientCreated(networkManager); } /// /// Validate shutting down a second time does not cause an exception. - /// + ///
[UnityTest] public IEnumerator ValidateShutdown([Values] ShutdownChecks shutdownCheck) { @@ -132,7 +128,7 @@ public IEnumerator ValidateShutdown([Values] ShutdownChecks shutdownCheck) } else { - // For this test (simplify the complexity) with a late joining client, just remove the + // For this test (simplify the complexity) with a late joining client, just remove the // in-scene placed NetworkObject prior to the client connecting // (We are testing the shutdown sequence) var spawnedObjects = m_ServerNetworkManager.SpawnManager.SpawnedObjectsList.ToList(); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs index ccfbf25b05..d5eb5f427c 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs @@ -59,19 +59,6 @@ protected override void OnServerAndClientsCreated() base.OnServerAndClientsCreated(); } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - foreach (var networkPrfab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) - { - if (networkPrfab.Prefab == null) - { - continue; - } - networkManager.NetworkConfig.Prefabs.Add(networkPrfab); - } - base.OnNewClientCreated(networkManager); - } - private bool DidClientsSpawnInstance(NetworkObject serverObject, bool checkDestroyWithScene = false) { foreach (var networkManager in m_ClientNetworkManagers) diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs index 20087214c0..5acf256de1 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs @@ -57,15 +57,6 @@ protected override IEnumerator OnStartedServerAndClients() return base.OnStartedServerAndClients(); } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) - { - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - } - base.OnNewClientCreated(networkManager); - } - protected override void OnNewClientStarted(NetworkManager networkManager) { m_ServerNetworkManager.SceneManager.SetClientSynchronizationMode(LoadSceneMode.Additive); diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs index cd8f6a1bc7..84829a6c8e 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingWorldPositionStaysTests.cs @@ -218,14 +218,6 @@ protected override void OnServerAndClientsCreated() base.OnServerAndClientsCreated(); } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) - { - networkManager.NetworkConfig.Prefabs.Add(networkPrefab); - } - } - private bool HaveAllClientsSpawnedObjects() { foreach (var client in m_ClientNetworkManagers) diff --git a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs index 16bc79e900..7bfe12083f 100644 --- a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs +++ b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs @@ -129,11 +129,10 @@ protected override void OnNewClientCreated(NetworkManager networkManager) { networkManager.NetworkConfig.EnableSceneManagement = m_SceneManagementEnabled; networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Add(PrefabTestConfig.Instance.TestPrefabs); - base.OnNewClientCreated(networkManager); } /// - /// Validates that all spawned NetworkObjects are present and their corresponding + /// Validates that all spawned NetworkObjects are present and their corresponding /// GlobalObjectIdHash values match /// private bool ValidateAllClientsSpawnedObjects() @@ -220,7 +219,7 @@ public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods insta yield return CreateAndStartNewClient(); var spawnManager = m_ServerNetworkManager.SpawnManager; - // If scene management is enabled, then we want to verify against the editor + // If scene management is enabled, then we want to verify against the editor // assigned in-scene placed NetworkObjects if (m_SceneManagementEnabled) { @@ -287,7 +286,7 @@ public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods insta [UnityTest] public IEnumerator TestsInstantiateAndSpawnErrors([Values] InstantiateAndSpawnMethods instantiateAndSpawnType) { - // If scene management is enabled, then we want to verify against the editor + // If scene management is enabled, then we want to verify against the editor // assigned in-scene placed NetworkObjects if (m_SceneManagementEnabled) { @@ -328,7 +327,7 @@ public IEnumerator TestsInstantiateAndSpawnErrors([Values] InstantiateAndSpawnMe m_ServerNetworkManager.Shutdown(); LogAssert.Expect(LogType.Warning, NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown]); InstantiateAndSpawn(m_ObjectsToSpawn[0], instantiateAndSpawnType); - // The not listening error can only happen when trying to instantiate and spawn on a Network Prefab + // The not listening error can only happen when trying to instantiate and spawn on a Network Prefab if (instantiateAndSpawnType == InstantiateAndSpawnMethods.NetworkObject) { LogAssert.Expect(LogType.Error, NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NoActiveSession]); From 0e1ae7e626e80bf0ab5cb6c486c078ef6d3396f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Tue, 1 Apr 2025 00:37:45 +0200 Subject: [PATCH 214/236] chore: [2.X] Updated CODEOWNERS file (#3372) Backport of https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3371 This PR updates CODEOWNERS file to mainly account for recent creation of netcode-qa group. The reasoning is: - @michalChrobot and @miniwolf were removed since they are part of @Unity-Technologies/netcode-qa group which was added - @fluong6 was removed since he's not focusing on NGO right now and we can lower the noise for him Co-authored-by: Noel Stephens --- .github/CODEOWNERS | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a7cd830adc..905922d636 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,10 +2,10 @@ # Order is important; the last matching pattern takes the most precedence. * @Unity-Technologies/multiplayer-sdk -*.asmdef @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM -package.json @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM -AssemblyInfo.cs @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM -.editorconfig @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM -.gitignore @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM -.github/ @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM -.yamato/ @miniwolf @NoelStephensUnity @fluong6 @michalChrobot @EmandM +*.asmdef @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa +package.json @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa +AssemblyInfo.cs @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa +.editorconfig @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa +.gitignore @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa +.github/ @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa +.yamato/ @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa From cad4b4cd9e69e0fa70f47e127c40ed5ebebac77d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 3 Apr 2025 02:59:54 -0500 Subject: [PATCH 215/236] fix: buffered linear interpolator jitter and exposing more properties (#3355) After doing some testing I noticed a few anomalies with the updated `BufferedLinearInterpolator`. As it turns out, there are more than one approaches a user might need depending upon what they are trying to accomplish and each option really performs best when tweaking some properties within the `BufferedLinearInterpolator` that were not completely exposed. ### Various options: - You might want to enable, disable, or adjust the maximum interpolation time of the last smooth lerp phase for each interpolator type. - You might want to manually adjust the tick latency offset (`NetworkTransform.InterpolationBufferTickOffset`) used depending upon context. - When using a client-server topology, you might want this to be set to something like 0 or 1 (depending upon interpolator). - You might want this value to be higher (like say +2-3 depending on latency and interpolation type) on non-authority instances where the authority is some other client and not the host/server. ### This PR renames `Lerp` to `LegacyLerp` and adds a 3rd new `Lerp` interpolator type: #### Lerp (new) Uses a 1 to 2 phase approach that lerps towards the target, lerps towards the next target (if one exists) ahead by 1 frame delta, blends the two results, and (optionally) smooth the final value. - The first phase lerps towards the current tick state update being processed. - The fourth phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of 1.0f minus the max interpolation time. ## Changelog - Added: `Lerp` interpolation type that still uses a lerp approach but uses the new buffer consumption logic. - Added: Property to enable or disable lerp smoothing for position, rotation, and scale interpolators. - Added: `NetworkTransform.InterpolationBufferTickOffset` static property to provide users with a way to increase or decrease the time marker where interpolators will pull state update from the queue. - Fixed: Issue where the time delta that interpolators used would not be properly updated during multiple fixed update invocations within the same player loop frame. - Changed: The original `Lerp` to be renamed to `LegacyLerp`. ## Testing and Documentation - Includes integration test updates. - Includes documentation for public API entry points. - Includes updates to public documentation for `NetworkTransform`. (Will be adding to [PR-1443](https://github.com/Unity-Technologies/com.unity.multiplayer.docs/pull/1443)) --------- Co-authored-by: Emma --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 +- .../Editor/NetworkTransformEditor.cs | 38 +- .../BufferedLinearInterpolator.cs | 329 ++++++---- .../BufferedLinearInterpolatorFloat.cs | 14 +- .../BufferedLinearInterpolatorQuaternion.cs | 11 +- .../BufferedLinearInterpolatorVector3.cs | 23 +- .../Runtime/Components/NetworkTransform.cs | 609 ++++++++++++++---- .../Runtime/Core/NetworkManager.cs | 19 + .../Messaging/Messages/TimeSyncMessage.cs | 1 + .../Runtime/Timing/NetworkTimeSystem.cs | 14 +- .../Runtime/NetcodeIntegrationTest.cs | 3 + .../NetworkTransform/NetworkTransformBase.cs | 17 +- .../NetworkTransformGeneral.cs | 65 +- .../NetworkTransform/NetworkTransformTests.cs | 66 +- .../Runtime/TransformInterpolationTests.cs | 2 +- .../NestedNetworkTransformTests.cs | 17 +- 16 files changed, 910 insertions(+), 325 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 73ed64a735..74718c95b9 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,6 +11,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added `NetworkManager.OnPreShutdown` which is called before the NetworkManager cleans up and shuts down. (#3366) +- Added `Lerp` interpolation type that still uses a lerp approach but uses the new buffer consumption logic. (#3355) +- Added property to enable or disable lerp smoothing for position, rotation, and scale interpolators. (#3355) +- Added `NetworkTransform.InterpolationBufferTickOffset` static property to provide users with a way to increase or decrease the time marker where interpolators will pull state update from the queue. (#3355) - Added interpolator types as an inspector view selection for position, rotation, and scale. (#3337) - Added a new smooth dampening interpolator type that provides a nice balance between precision and smoothing results. (#3337) - Added `NetworkTimeSystem.TickLatency` property that provides the average latency of a client. (#3337) @@ -19,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where the time delta that interpolators used would not be properly updated during multiple fixed update invocations within the same player loop frame. (#3355) - Fixed issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. (#3350) - Fixed issue when using a distributed authority network topology and scene management was disabled clients would not be able to spawn any new network prefab instances until synchronization was complete. (#3350) - Fixed issue where an owner that changes ownership, when using a distributed authority network topology, could yield identical previous and current owner identifiers. This could also cause `NetworkTransform` to fail to change ownership which would leave the previous owner still subscribed to network tick events. (#3347) @@ -39,8 +43,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed the original `Lerp` interpolation type to `LegacyLerp`. (#3355) - Changed `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as being deprecated since this method is only used for internal testing purposes. (#3337) -- Ensured that a useful error is thrown when attempting to build a dedicated server with Unity Transport that uses websockets. (#3336) +- Changed error thrown when attempting to build a dedicated server with Unity Transport that uses websockets to provide more useful information to the user. (#3336) - Changed root in-scene placed `NetworkObject` instances now will always have either the `Distributable` permission set unless the `SessionOwner` permission is set. (#3305) - Changed the `DestroyObject` message to reduce the serialized message size and remove the unnecessary message field. (#3304) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index b04ad3f724..53d09b782f 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -31,6 +31,9 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_PositionInterpolationTypeProperty; private SerializedProperty m_RotationInterpolationTypeProperty; private SerializedProperty m_ScaleInterpolationTypeProperty; + private SerializedProperty m_PositionLerpSmoothing; + private SerializedProperty m_RotationLerpSmoothing; + private SerializedProperty m_ScaleLerpSmoothing; private SerializedProperty m_PositionMaximumInterpolationTimeProperty; private SerializedProperty m_RotationMaximumInterpolationTimeProperty; @@ -77,6 +80,11 @@ public override void OnEnable() m_ScaleInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleInterpolationType)); m_ScaleMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleMaxInterpolationTime)); + m_PositionLerpSmoothing = serializedObject.FindProperty(nameof(NetworkTransform.PositionLerpSmoothing)); + m_RotationLerpSmoothing = serializedObject.FindProperty(nameof(NetworkTransform.RotationLerpSmoothing)); + m_ScaleLerpSmoothing = serializedObject.FindProperty(nameof(NetworkTransform.ScaleLerpSmoothing)); + + m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization)); m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression)); @@ -198,36 +206,42 @@ private void DisplayNetworkTransformProperties() if (networkTransform.SynchronizePosition) { DrawPropertyField(m_PositionInterpolationTypeProperty); - // Only display when using Lerp. - if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + + BeginIndent(); + if (networkTransform.PositionInterpolationType != NetworkTransform.InterpolationTypes.SmoothDampening) { - BeginIndent(); DrawPropertyField(m_SlerpPosition); + } + DrawPropertyField(m_PositionLerpSmoothing); + if (networkTransform.PositionLerpSmoothing) + { DrawPropertyField(m_PositionMaximumInterpolationTimeProperty); - EndIndent(); } + EndIndent(); } if (networkTransform.SynchronizeRotation) { DrawPropertyField(m_RotationInterpolationTypeProperty); - // Only display when using Lerp. - if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + + BeginIndent(); + DrawPropertyField(m_RotationLerpSmoothing); + if (networkTransform.RotationLerpSmoothing) { - BeginIndent(); DrawPropertyField(m_RotationMaximumInterpolationTimeProperty); - EndIndent(); } + EndIndent(); } if (networkTransform.SynchronizeScale) { DrawPropertyField(m_ScaleInterpolationTypeProperty); - // Only display when using Lerp. - if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + + BeginIndent(); + DrawPropertyField(m_ScaleLerpSmoothing); + if (networkTransform.ScaleLerpSmoothing) { - BeginIndent(); DrawPropertyField(m_ScaleMaximumInterpolationTimeProperty); - EndIndent(); } + EndIndent(); } EndIndent(); EditorGUILayout.Space(); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 0aabd0226e..538a6ba1a1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -115,40 +115,35 @@ public BufferedItem(T item, double timeSent, int itemId) internal struct CurrentState { public BufferedItem? Target; - public double StartTime; public double EndTime; - public float TimeToTargetValue; - public float DeltaTime; + public double TimeToTargetValue; + public double DeltaTime; + public double MaxDeltaTime; + public double LastRemainingTime; public float LerpT; - + public bool TargetReached; public T CurrentValue; public T PreviousValue; + public T NextValue; - private float m_AverageDeltaTime; + private float m_CurrentDeltaTime; - public float AverageDeltaTime => m_AverageDeltaTime; - public float FinalTimeToTarget => TimeToTargetValue - DeltaTime; + public float CurrentDeltaTime => m_CurrentDeltaTime; + public double FinalTimeToTarget => Math.Max(0.0, TimeToTargetValue - DeltaTime); public void AddDeltaTime(float deltaTime) { - if (m_AverageDeltaTime == 0.0f) - { - m_AverageDeltaTime = deltaTime; - } - else - { - m_AverageDeltaTime += deltaTime; - m_AverageDeltaTime *= 0.5f; - } - DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); - LerpT = TimeToTargetValue == 0.0f ? 1.0f : DeltaTime / TimeToTargetValue; + m_CurrentDeltaTime = deltaTime; + DeltaTime = Math.Min(DeltaTime + deltaTime, TimeToTargetValue); + LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue); } - public void ResetDelta() + public void SetTimeToTarget(double timeToTarget) { - m_AverageDeltaTime = 0.0f; + LerpT = 0.0f; DeltaTime = 0.0f; + TimeToTargetValue = timeToTarget; } public bool TargetTimeAproximatelyReached() @@ -157,22 +152,27 @@ public bool TargetTimeAproximatelyReached() { return false; } - return m_AverageDeltaTime >= FinalTimeToTarget; + return FinalTimeToTarget <= m_CurrentDeltaTime * 0.5f; } public void Reset(T currentValue) { Target = null; CurrentValue = currentValue; + NextValue = currentValue; PreviousValue = currentValue; - // When reset, we consider ourselves to have already arrived at the target (even if no target is set) + TargetReached = false; LerpT = 0.0f; EndTime = 0.0; StartTime = 0.0; - ResetDelta(); + TimeToTargetValue = 0.0f; + DeltaTime = 0.0f; + m_CurrentDeltaTime = 0.0f; } } + internal bool LerpSmoothEnabled; + /// /// Determines how much smoothing will be applied to the 2nd lerp when using the (i.e. lerping and not smooth dampening). /// @@ -210,16 +210,6 @@ public void Reset(T currentValue) ///
private T m_RateOfChange; - /// - /// Represents the predicted rate of change for the value being interpolated when smooth dampening is enabled. - /// - private T m_PredictedRateOfChange; - - /// - /// When true, the value is an angular numeric representation. - /// - private protected bool m_IsAngularValue; - /// /// Resets interpolator to the defaults. /// @@ -230,7 +220,6 @@ public void Clear() m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); m_RateOfChange = default; - m_PredictedRateOfChange = default; } /// @@ -241,21 +230,18 @@ public void Clear() /// /// The target value to reset the interpolator to /// The current server time - /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. - public void ResetTo(T targetValue, double serverTime, bool isAngularValue = false) + public void ResetTo(T targetValue, double serverTime) { // Clear the interpolator Clear(); - InternalReset(targetValue, serverTime, isAngularValue); + InternalReset(targetValue, serverTime); } - private void InternalReset(T targetValue, double serverTime, bool isAngularValue = false, bool addMeasurement = true) + private void InternalReset(T targetValue, double serverTime, bool addMeasurement = true) { m_RateOfChange = default; - m_PredictedRateOfChange = default; // Set our initial value InterpolateState.Reset(targetValue); - m_IsAngularValue = isAngularValue; if (addMeasurement) { @@ -264,78 +250,102 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue } } - #region Smooth Dampening Interpolation + #region Smooth Dampening and Lerp Ahead Interpolation Handling /// - /// TryConsumeFromBuffer: Smooth Dampening Version + /// TryConsumeFromBuffer: Smooth Dampening and Lerp Ahead Version /// - /// render time: the time in "ticks ago" relative to the current tick latency - /// minimum time delta (defaults to tick frequency) - /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer - private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) + /// render time: the time in "ticks ago" relative to the current tick latency. + /// minimum time delta (defaults to tick frequency). + /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer. + /// when true, the predicted target will lerp towards the next target by the current delta. + private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime) { - if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime - && (InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) + BufferedItem? previousItem = null; + var startTime = 0.0; + var alreadyHasBufferItem = false; + var noStateSet = !InterpolateState.Target.HasValue; + var potentialItemNeedsProcessing = false; + + // In the event there is nothing left in the queue (i.e. motion/change stopped), we still need to determine if the target has been reached. + if (!noStateSet && m_BufferQueue.Count == 0) { - BufferedItem? previousItem = null; - var startTime = 0.0; - var alreadyHasBufferItem = false; - while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) + if (!InterpolateState.TargetReached) { - // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. - if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + } + return; + } + + // Continue to process any remaining state updates in the queue (if any) + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) + { + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } + + if (!noStateSet) + { + potentialItemNeedsProcessing = ((potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent); + if (!InterpolateState.TargetReached) { - break; + InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); } + } - // If we haven't set a target or the potential item's time sent is less that the current target's time sent - // then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there - // was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under - // this scenario, we just want to continue pulling items from the queue until the last item pulled from the - // queue is greater than the redner time or greater than the currently targeted item. - if (!InterpolateState.Target.HasValue || - ((potentialItem.TimeSent <= renderTime) && InterpolateState.Target.Value.TimeSent <= potentialItem.TimeSent)) + // If we haven't set a target or we have another item that needs processing. + if (noStateSet || potentialItemNeedsProcessing) + { + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { - if (m_BufferQueue.TryDequeue(out BufferedItem target)) + if (!InterpolateState.Target.HasValue) { - if (!InterpolateState.Target.HasValue) + InterpolateState.Target = target; + alreadyHasBufferItem = true; + InterpolateState.NextValue = InterpolateState.CurrentValue; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.SetTimeToTarget(minDeltaTime); + startTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.TargetReached = false; + InterpolateState.MaxDeltaTime = maxDeltaTime; + } + else + { + if (!alreadyHasBufferItem) { - InterpolateState.Target = target; - alreadyHasBufferItem = true; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.TimeToTargetValue = minDeltaTime; + InterpolateState.LastRemainingTime = InterpolateState.FinalTimeToTarget; + InterpolateState.TargetReached = false; + InterpolateState.MaxDeltaTime = maxDeltaTime; + InterpolateState.PreviousValue = InterpolateState.NextValue; startTime = InterpolateState.Target.Value.TimeSent; } - else - { - if (!alreadyHasBufferItem) - { - alreadyHasBufferItem = true; - startTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.LerpT = 0.0f; - } - // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated - // for each item as opposed to losing the resolution of the values. - InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); - - InterpolateState.Target = target; - } - InterpolateState.ResetDelta(); + InterpolateState.SetTimeToTarget(Math.Max(target.TimeSent - startTime, minDeltaTime)); + InterpolateState.Target = target; } } - else - { - break; - } + } + else + { + break; + } - if (!InterpolateState.Target.HasValue) - { - break; - } - previousItem = potentialItem; + if (!InterpolateState.Target.HasValue) + { + break; } + previousItem = potentialItem; + } + } + + internal void ResetCurrentState() + { + if (InterpolateState.Target.HasValue) + { + InterpolateState.Reset(InterpolateState.CurrentValue); + m_RateOfChange = default; } } @@ -350,23 +360,56 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m /// The tick latency in relative local time. /// The minimum time delta between the current and target value. /// The maximum time delta between the current and target value. + /// Determines whether to use smooth dampening or lerp interpolation type. /// The newly interpolated value of type 'T' - internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) + internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool lerp) { TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); - // Only interpolate when there is a start and end point and we have not already reached the end value + // Only begin interpolation when there is a start and end point if (InterpolateState.Target.HasValue) { - InterpolateState.AddDeltaTime(deltaTime); - - // Smooth dampen our current time - var current = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); - // Smooth dampen a predicted time based on our average delta time - var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + (InterpolateState.AverageDeltaTime * 2)); - // Lerp between the current and predicted. - // Note: Since smooth dampening cannot over shoot, both current and predict will eventually become the same or will be very close to the same. - // Upon stopping motion, the final resing value should be a very close aproximation of the authority side. - InterpolateState.CurrentValue = Interpolate(current, predict, deltaTime); + // As long as the target hasn't been reached, interpolate or smooth dampen. + if (!InterpolateState.TargetReached) + { + // Increases the time delta relative to the time to target. + // Also calculates the LerpT and LerpTPredicted values. + InterpolateState.AddDeltaTime(deltaTime); + // SmoothDampen + if (!lerp) + { + InterpolateState.NextValue = SmoothDamp(InterpolateState.NextValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue * InterpolateState.LerpT, deltaTime); + } + else// Lerp + { + InterpolateState.NextValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + } + + // If lerp smoothing is enabled, then smooth current value towards the target value + if (LerpSmoothEnabled) + { + // Apply the smooth lerp to the target to help smooth the final value. + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, Mathf.Clamp(1.0f - MaximumInterpolationTime, 0.0f, 1.0f)); + } + else + { + // Otherwise, just assign the target value. + InterpolateState.CurrentValue = InterpolateState.NextValue; + } + } + else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + if (m_BufferQueue.Count == 0) + { + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime) + { + InterpolateState.Reset(InterpolateState.CurrentValue); + } + } } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; @@ -381,7 +424,6 @@ internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, /// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern. /// /// - /// private void TryConsumeFromBuffer(double renderTime, double serverTime) { if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime)) @@ -406,7 +448,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { InterpolateState.Target = target; alreadyHasBufferItem = true; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.NextValue = InterpolateState.CurrentValue; InterpolateState.StartTime = target.TimeSent; InterpolateState.EndTime = target.TimeSent; } @@ -416,12 +458,13 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { alreadyHasBufferItem = true; InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.NextValue = InterpolateState.CurrentValue; + InterpolateState.TargetReached = false; } InterpolateState.EndTime = target.TimeSent; InterpolateState.Target = target; + InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; } - InterpolateState.ResetDelta(); } } else @@ -452,32 +495,43 @@ public T Update(float deltaTime, double renderTime, double serverTime) { TryConsumeFromBuffer(renderTime, serverTime); // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Target.HasValue) + if (InterpolateState.Target.HasValue && !InterpolateState.TargetReached) { // The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact // this specific behavior. - float t = 1.0f; - double range = InterpolateState.EndTime - InterpolateState.StartTime; - if (range > k_SmallValue) + InterpolateState.LerpT = 1.0f; + if (InterpolateState.TimeToTargetValue > k_SmallValue) { - t = (float)((renderTime - InterpolateState.StartTime) / range); + InterpolateState.LerpT = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f); + } - if (t < 0.0f) - { - t = 0.0f; - } + var target = Interpolate(InterpolateState.NextValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - if (t > MaxInterpolationBound) // max extrapolation - { - // TODO this causes issues with teleport, investigate - t = 1.0f; - } + if (LerpSmoothEnabled) + { + // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime); + } + else + { + InterpolateState.CurrentValue = target; + } + // Determine if we have reached our target + InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + } + else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + if (m_BufferQueue.Count == 0 && InterpolateState.TargetReached) + { + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion. + { + InterpolateState.Reset(InterpolateState.CurrentValue); } - var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); - - // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. - var secondLerpTime = Mathf.Clamp(deltaTime / Mathf.Max(deltaTime, MaximumInterpolationTime), deltaTime, 1.0f); - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, secondLerpTime); } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; @@ -501,9 +555,9 @@ public T Update(float deltaTime, NetworkTime serverTime) /// /// Used for internal testing /// - internal T UpdateInternal(float deltaTime, NetworkTime serverTime) + internal T UpdateInternal(float deltaTime, NetworkTime serverTime, int ticksAgo = 1) { - return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); + return Update(deltaTime, serverTime.TimeTicksAgo(ticksAgo).Time, serverTime.Time); } /// @@ -524,7 +578,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) // Clear the interpolator Clear(); // Reset to the new value but don't automatically add the measurement (prevents recursion) - InternalReset(newMeasurement, sentTime, m_IsAngularValue, false); + InternalReset(newMeasurement, sentTime, false); m_LastMeasurementAddedTime = sentTime; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues @@ -533,7 +587,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) return; } - // Drop measurements that are received out of order/late + // Drop measurements that are received out of order/late (i.e. user unreliable delta) if (sentTime > m_LastMeasurementAddedTime || m_BufferCount == 0) { m_BufferCount++; @@ -631,9 +685,12 @@ internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) m_BufferQueue.Enqueue(entry); } InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); - var end = InterpolateState.Target.Value; - end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); - InterpolateState.Target = end; + if (InterpolateState.Target.HasValue) + { + var end = InterpolateState.Target.Value; + end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.Target = end; + } InLocalSpace = inLocalSpace; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs index 9d3765fb40..77583468a7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -9,34 +10,31 @@ namespace Unity.Netcode public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator { /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override float InterpolateUnclamped(float start, float end, float time) { return Mathf.LerpUnclamped(start, end, time); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override float Interpolate(float start, float end, float time) { return Mathf.Lerp(start, end, time); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override bool IsAproximately(float first, float second, float precision = 1E-07F) { return Mathf.Approximately(first, second); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { - if (m_IsAngularValue) - { - return Mathf.SmoothDampAngle(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); - } - else - { - return Mathf.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); - } + return Mathf.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 8eb6d29bcc..5b8033a977 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -21,6 +22,7 @@ public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) { if (IsSlerp) @@ -34,6 +36,7 @@ protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) { if (IsSlerp) @@ -47,6 +50,7 @@ protected override Quaternion Interpolate(Quaternion start, Quaternion end, floa } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Quaternion rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { Vector3 currentEuler = current.eulerAngles; @@ -61,12 +65,17 @@ private protected override Quaternion SmoothDamp(Quaternion current, Quaternion } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) { - return (1.0f - Quaternion.Dot(first, second)) <= precision; + return Mathf.Abs(first.x - second.x) <= precision && + Mathf.Abs(first.y - second.y) <= precision && + Mathf.Abs(first.z - second.z) <= precision && + Mathf.Abs(first.w - second.w) <= precision; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal override Quaternion OnConvertTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) { if (inLocalSpace) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index 2bb32d5402..aa5a739683 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -1,3 +1,5 @@ +using System; +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -13,6 +15,7 @@ public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator public bool IsSlerp; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time) { if (IsSlerp) @@ -26,6 +29,7 @@ protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, floa } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) { if (IsSlerp) @@ -39,6 +43,7 @@ protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal override Vector3 OnConvertTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) { if (inLocalSpace) @@ -53,25 +58,19 @@ protected internal override Vector3 OnConvertTransformSpace(Transform transform, } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) { - return Vector3.Distance(first, second) <= precision; + return Math.Round(Mathf.Abs(first.x - second.x), 2) <= precision && + Math.Round(Mathf.Abs(first.y - second.y), 2) <= precision && + Math.Round(Mathf.Abs(first.z - second.z), 2) <= precision; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) { - if (m_IsAngularValue) - { - current.x = Mathf.SmoothDampAngle(current.x, target.x, ref rateOfChange.x, duration, maxSpeed, deltaTime); - current.y = Mathf.SmoothDampAngle(current.y, target.y, ref rateOfChange.y, duration, maxSpeed, deltaTime); - current.z = Mathf.SmoothDampAngle(current.z, target.z, ref rateOfChange.z, duration, maxSpeed, deltaTime); - return current; - } - else - { - return Vector3.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); - } + return Vector3.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d0951c6cfb..aeb9687e7b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -16,7 +16,6 @@ namespace Unity.Netcode.Components [AddComponentMenu("Netcode/Network Transform")] public class NetworkTransform : NetworkBehaviour { - #if UNITY_EDITOR internal virtual bool HideInterpolateValue => false; @@ -936,26 +935,92 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #region PROPERTIES AND GENERAL METHODS + /// + /// Pertains to Owner Authority and Interpolation
+ /// When enabled (default), 1 additional tick is added to the total number of ticks used to calculate the tick latency ("ticks ago") as a time. + /// This calculated time value is passed into the respective and used to determine if any pending + /// state updates in the queue should be processed. + /// The additional tick value is only applied when: + /// + /// The is using a authority mode. + /// The non-authority instance is a client (i.e. not host or server). + /// The network topology being used is . + /// + ///
+ /// + /// When calculating the total tick latency as time value, the is added to the + /// and if this property is enabled (and the conditions above are met) an additional tick is added to the final resultant value.
+ /// Note: The reason behind this additional tick latency value is due to the 2 RTT timespan when a client state update is sent to the host or server (1 RTT) + /// and the host or server relays this state update to all non-authority instances (1 RTT). + ///
+ public bool AutoOwnerAuthorityTickOffset = true; + /// /// The different interpolation types used with to help smooth interpolation results. + /// Interpolation types can be changed during runtime. /// public enum InterpolationTypes { /// - /// Uses lerping and yields a linear progression between two values. + /// Legacy Lerp (original NGO lerping model)
+ /// Uses a 1 to 2 phase lerp approach where:
+ /// + /// The first phase lerps from the previous state update value to the next state update value. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. + /// + ///
+ /// + /// For more information:
+ /// + /// + /// + /// + /// + /// + /// + /// + ///
+ LegacyLerp, + /// + /// Lerp (maintains time to target when under higher conditions)
+ /// Uses a 1 to 2 phase interpolation approach where:
+ /// + /// The first phase lerps from the previous state update value to the next state update value. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of 1.0 minus the respective maximum interpolation time. + /// ///
/// /// For more information:
- /// -
- /// -
- /// -
+ /// + /// + /// + /// + /// + /// + /// + /// ///
Lerp, /// - /// Uses a smooth dampening approach for interpolating between two data points and adjusts based on rate of change. + /// Smooth Dampening (maintains time to target when under higher conditions)
+ /// Uses a 1 to 2 phase smooth dampening approach where:
+ /// + /// The first phase smooth dampens towards the current tick state update being processed by the accumulated delta time relative to the time to target. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// ///
/// - /// Unlike , there are no additional values needed to be adjusted for this interpolation type. + /// Note: Smooth dampening is computationally more expensive than the and approaches.
+ /// It is recommended to turn lerp smoothing off or adjust the maximum interpolation time to a lower value if you want a more precise end result. + /// For more information:
+ /// + /// + /// + /// + /// + /// + /// + /// ///
SmoothDampening } @@ -964,76 +1029,118 @@ public enum InterpolationTypes /// The position interpolation type to use for the instance. ///
/// - /// - yields a traditional linear result.
- /// - adjusts based on the rate of change.
- /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
- /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ /// + /// Yields the original Netcode for GameObjects lerp result. + /// Uses the newer linear buffer queue consumption approach that maintains a consistent time to the next target. + /// Uses the newer linear buffer queue consumption approach and adjusts based on the rate of change. + /// + /// Things to consider:
+ /// + /// You can have mixed interpolation types between position, rotation, and scale on the same instance. + /// You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion. + /// ///
[Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes PositionInterpolationType; + private InterpolationTypes m_PreviousPositionInterpolationType; /// /// The rotation interpolation type to use for the instance. /// /// - /// - yields a traditional linear result.
- /// - adjusts based on the rate of change.
- /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
- /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ /// + /// Yields the original Netcode for GameObjects lerp result. + /// Uses the newer linear buffer queue consumption approach that maintains a consistent time to the next target. + /// Uses the newer linear buffer queue consumption approach and adjusts based on the rate of change. + /// + /// Things to consider:
+ /// + /// You can have mixed interpolation types between position, rotation, and scale on the same instance. + /// You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion. + /// ///
[Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes RotationInterpolationType; + private InterpolationTypes m_PreviousRotationInterpolationType; /// /// The scale interpolation type to use for the instance. /// /// - /// - yields a traditional linear result.
- /// - adjusts based on the rate of change.
- /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
- /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ /// + /// Yields the original Netcode for GameObjects lerp result. + /// Uses the newer linear buffer queue consumption approach that maintains a consistent time to the next target. + /// Uses the newer linear buffer queue consumption approach and adjusts based on the rate of change. + /// + /// Things to consider:
+ /// + /// You can have mixed interpolation types between position, rotation, and scale on the same instance. + /// You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion. + /// ///
[Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes ScaleInterpolationType; + private InterpolationTypes m_PreviousScaleInterpolationType; /// - /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
- /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + /// Controls position interpolation smoothing. ///
/// - /// - Only used When is enabled and using .
- /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . ///
+ public bool PositionLerpSmoothing = true; + private bool m_PreviousPositionLerpSmoothing; + + /// + /// The position interoplation maximum interpolation time.
+ /// + /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). + /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). + /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. linear velocity or the like). + /// + ///
[Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float PositionMaxInterpolationTime = 0.1f; /// - /// The rotation interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
- /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + /// Controls rotation interpolation smoothing. ///
/// - /// - Only used When is enabled and using .
- /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . ///
+ public bool RotationLerpSmoothing = true; + private bool m_PreviousRotationLerpSmoothing; + + /// + /// The rotation interoplation maximum interpolation time.
+ /// + /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). + /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). + /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. angular velocity). + /// + ///
[Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float RotationMaxInterpolationTime = 0.1f; /// - /// The scale interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
- /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + /// Controls scale interpolation smoothing. ///
/// - /// - Only used When is enabled and using .
- /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . ///
+ public bool ScaleLerpSmoothing = true; + private bool m_PreviousScaleLerpSmoothing; + + /// + /// The scale interoplation maximum interpolation time.
+ /// + /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). + /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). + /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value. + /// + ///
[Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float ScaleMaxInterpolationTime = 0.1f; @@ -1044,7 +1151,7 @@ public enum InterpolationTypes public enum AuthorityModes { /// - /// Server pushes transform state updates + /// Server pushes transform state updates. /// Server, /// @@ -1071,27 +1178,29 @@ public enum AuthorityModes /// This can help to reduce out of sync updates that can lead to slight jitter between a parent and its child/children. /// /// - /// - If this is set on a child and the parent does not have this set then the child will not be tick synchronized with its parent.
- /// - If the parent instance does not send any state updates, the children will still send state updates when exceeding axis delta threshold.
- /// - This does not need to be set on children to be applied. + /// + /// If this is set on a child and the parent does not have this set then the child will not be tick synchronized with its parent. + /// If the parent instance does not send any state updates, the children will still send state updates when exceeding axis delta threshold. + /// This does not need to be set on children to be applied. + /// ///
[Tooltip("When enabled, any parented children of this instance will send a state update when this instance sends a state update. If this instance doesn't send a state update, the children will still send state updates when reaching their axis specified threshold delta. Children do not have to have this setting enabled.")] public bool TickSyncChildren = false; /// - /// The default position change threshold value. + /// The default position change threshold value.
/// Any changes above this threshold will be replicated. ///
public const float PositionThresholdDefault = 0.001f; /// - /// The default rotation angle change threshold value. + /// The default rotation angle change threshold value.
/// Any changes above this threshold will be replicated. ///
public const float RotAngleThresholdDefault = 0.01f; /// - /// The default scale change threshold value. + /// The default scale change threshold value.
/// Any changes above this threshold will be replicated. ///
public const float ScaleThresholdDefault = 0.01f; @@ -1123,12 +1232,13 @@ public enum AuthorityModes /// are sent using a reliable fragmented sequenced network delivery. ///
/// - /// The following more critical state updates are still sent as reliable fragmented sequenced: - /// - The initial synchronization state update - /// - The teleporting state update. - /// - When using half float precision and the `NetworkDeltaPosition` delta exceeds the maximum delta forcing the axis in - /// question to be collapsed into the core base position, this state update will be sent as reliable fragmented sequenced. - /// + /// The following more critical state updates are still sent as reliable fragmented sequenced:
+ /// + /// The initial synchronization state update. + /// The teleporting state update. + /// When using half float precision and the `NetworkDeltaPosition` delta exceeds the maximum delta forcing the axis in + /// question to be collapsed into the core base position, this state update will be sent as reliable fragmented sequenced. + /// /// In order to preserve a continual consistency of axial values when unreliable delta messaging is enabled (due to the /// possibility of dropping packets), NetworkTransform instances will send 1 axial frame synchronization update per /// second (only for the axis marked to synchronize are sent as reliable fragmented sequenced) as long as a delta state @@ -1250,8 +1360,16 @@ internal bool SynchronizeScale /// The rotation threshold value that triggers a delta state update by the authoritative instance. ///
/// - /// Minimum Value: 0.00001 - /// Maximum Value: 360.0 + /// + /// + /// Minimum Value + /// 0.00001 + /// + /// + /// Maximum Value + /// 360.0 + /// + /// /// [Range(0.00001f, 360.0f)] public float RotAngleThreshold = RotAngleThresholdDefault; @@ -1265,7 +1383,7 @@ internal bool SynchronizeScale public float ScaleThreshold = ScaleThresholdDefault; /// - /// Enable this on the authority side for quaternion synchronization + /// Enable this on the authority side for quaternion synchronization. /// /// /// This is synchronized by authority. During runtime, this should only be changed by the @@ -1281,7 +1399,7 @@ internal bool SynchronizeScale /// /// This has a lower precision than half float precision. Recommended only for low precision /// scenarios. provides better precision at roughly half - /// the cost of a full quaternion update. + /// the cost of a full quaternion update.
/// This is synchronized by authority. During runtime, this should only be changed by the /// authoritative side. Non-authoritative instances will be overridden by the next /// authoritative state update. @@ -1290,7 +1408,7 @@ internal bool SynchronizeScale public bool UseQuaternionCompression = false; /// - /// Enable this to use half float precision for position, rotation, and scale. + /// Enable this to use half float precision for position, rotation, and scale.
/// When enabled, delta position synchronization is used. ///
/// @@ -1375,19 +1493,27 @@ internal bool SynchronizeScale /// Helper method that returns the space relative position of the transform. ///
/// - /// If InLocalSpace is then it returns the transform.localPosition - /// If InLocalSpace is then it returns the transform.position - /// When invoked on the non-authority side: - /// If is true then it will return the most + /// + /// If InLocalSpace is then it returns the transform.localPosition. + /// If InLocalSpace is then it returns the transform.position. + /// + /// + /// + /// When invoked on the non-authority side: + /// If is true then it will return the most /// current authority position from the most recent state update. This can be useful /// if interpolation is enabled and you need to determine the final target position. - /// When invoked on the authority side: - /// It will always return the space relative position. + /// + /// + /// When invoked on the authority side: + /// It will always return the space relative position. + /// + /// /// /// - /// Authority always returns the space relative transform position (whether true or false). - /// Non-authority: - /// When false (default): returns the space relative transform position + /// Authority always returns the space relative transform position (whether true or false).
+ /// Non-authority:
+ /// When false (default): returns the space relative transform position.
/// When true: returns the authority position from the most recent state update. /// /// @@ -1417,19 +1543,27 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) /// Helper method that returns the space relative rotation of the transform. ///
/// - /// If InLocalSpace is then it returns the transform.localRotation - /// If InLocalSpace is then it returns the transform.rotation - /// When invoked on the non-authority side: - /// If is true then it will return the most - /// current authority rotation from the most recent state update. This can be useful + /// + /// If InLocalSpace is then it returns the transform.localRotation. + /// If InLocalSpace is then it returns the transform.rotation. + /// + /// + /// + /// When invoked on the non-authority side: + /// If is true then it will return the most + /// current authority position from the most recent state update. This can be useful /// if interpolation is enabled and you need to determine the final target rotation. - /// When invoked on the authority side: - /// It will always return the space relative rotation. + /// + /// + /// When invoked on the authority side: + /// It will always return the space relative rotation. + /// + /// /// /// - /// Authority always returns the space relative transform rotation (whether true or false). - /// Non-authority: - /// When false (default): returns the space relative transform rotation + /// Authority always returns the space relative transform rotation (whether true or false).
+ /// Non-authority:
+ /// When false (default): returns the space relative transform rotation.
/// When true: returns the authority rotation from the most recent state update. /// /// @@ -1449,17 +1583,17 @@ public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) /// Helper method that returns the scale of the transform. ///
/// - /// When invoked on the non-authority side: + /// When invoked on the non-authority side:
/// If is true then it will return the most /// current authority scale from the most recent state update. This can be useful - /// if interpolation is enabled and you need to determine the final target scale. - /// When invoked on the authority side: + /// if interpolation is enabled and you need to determine the final target scale.
+ /// When invoked on the authority side:
/// It will always return the space relative scale. ///
/// - /// Authority always returns the space relative transform scale (whether true or false). - /// Non-authority: - /// When false (default): returns the space relative transform scale + /// Authority always returns the space relative transform scale (whether true or false).
+ /// Non-authority:
+ /// When false (default): returns the space relative transform scale.
/// When true: returns the authority scale from the most recent state update. /// /// @@ -3075,9 +3209,18 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf /// for all transform elements being monitored by /// (i.e. Position, Scale, and Rotation) ///
+ /// + /// All of three max interpolation time properties will have this maximum interpolation bound value applied:
+ /// -
+ /// -
+ /// -
+ ///
/// Maximum time boundary that can be used in a frame when interpolating between two values public void SetMaxInterpolationBound(float maxInterpolationBound) { + PositionMaxInterpolationTime = maxInterpolationBound; + RotationMaxInterpolationTime = maxInterpolationBound; + ScaleMaxInterpolationTime = maxInterpolationBound; m_RotationInterpolator.MaxInterpolationBound = maxInterpolationBound; m_PositionInterpolator.MaxInterpolationBound = maxInterpolationBound; m_ScaleInterpolator.MaxInterpolationBound = maxInterpolationBound; @@ -3388,7 +3531,6 @@ private void InternalInitialization(bool isOwnershipChange = false) // Determine if this is the first NetworkTransform in the associated NetworkObject's list m_IsFirstNetworkTransform = NetworkObject.NetworkTransforms[0] == this; - if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) { AuthorityMode = AuthorityModes.Owner; @@ -3456,6 +3598,14 @@ private void InternalInitialization(bool isOwnershipChange = false) } else { + // Always set these during initialization for non-authority so we can detect a change in interpolator types + m_PreviousPositionInterpolationType = PositionInterpolationType; + m_PreviousRotationInterpolationType = RotationInterpolationType; + m_PreviousScaleInterpolationType = ScaleInterpolationType; + m_PreviousPositionLerpSmoothing = PositionLerpSmoothing; + m_PreviousRotationLerpSmoothing = RotationLerpSmoothing; + m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; + // Non-authority needs to be added to updates for interpolation and applying state purposes m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true); // Remove this instance from the tick update @@ -3829,18 +3979,95 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #region UPDATES AND AUTHORITY CHECKS private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; +#if DEBUG_LINEARBUFFER +#if UNITY_EDITOR + // For debugging purposes + public struct BufferEntry + { + public double TimeSent; + public Vector3 Position; + } + public struct NTPositionStats + { + public int FrameCount; + public int FixedFrameCount; + public int FixedUpdatesPerFrameCount; + public int TimeSynchCount; + public int Tick; + public double Time; + public double TicksAgoTime; + public int BufferCount; + public int TickMeasured; + public float LerpT; + public double DeltaTime; + public double MaxDeltaTime; + public double TimeSent; + public double TimeToTargetValue; + public Vector3 PreviousValue; + public Vector3 NextValue; + public Vector3 CurrentValue; + public Vector3 TargetValue; + + public List Buffer; + } + + public Dictionary> PositionStats = new Dictionary>(); + public bool GatherStats; + private int m_LastTimeSyncCount; + + public void ClearStats() + { + PositionStats.Clear(); + } + + public BufferedLinearInterpolatorVector3 GetPositionInterpolator() + { + return m_PositionInterpolator; + } + + public BufferedLinearInterpolatorQuaternion GetRotationInterpolator() + { + return m_RotationInterpolator; + } +#endif + public int GetPositionBufferCount() + { + return m_PositionInterpolator.m_BufferQueue.Count; + } + + public double GetPositionCurrentStateTimeToTarget() + { + return m_PositionInterpolator.InterpolateState.TimeToTargetValue; + } + + public double GetPositionLerpT() + { + return m_PositionInterpolator.InterpolateState.LerpT; + } + + public double GetPositionLastRemainingTime() + { + return m_PositionInterpolator.InterpolateState.LastRemainingTime; + } +#endif + + + // Non-Authority private void UpdateInterpolation() { AdjustForChangeInTransformSpace(); - - var cachedServerTime = m_CachedNetworkManager.ServerTime.Time; + var timeSystem = m_CachedNetworkManager.ServerTime; + var currentTime = timeSystem.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; + var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else - var cachedDeltaTime = Time.deltaTime; + var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; #endif - var tickLatency = m_CachedNetworkManager.NetworkTimeSystem.TickLatency; + // Optional user defined tick offset to be used to push the "render time" (the time that will be used to determine if a state update is available) + // back in order to provide more room for the interpolator to interpolate towards when latency conditions are impacting the frequency that state + // updates are received. + var tickLatency = Mathf.Max(1, m_CachedNetworkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset); // If using an owner authoritative motion model if (!IsServerAuthoritative()) @@ -3858,59 +4085,162 @@ private void UpdateInterpolation() } } - var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; - // Smooth dampening specific: - // We clamp between tick rate and bit beyond the tick rate but not 2x tick rate (we predict 2x out) - var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; - // The 1.666667f value is a "magic" number tht lies between the FixedDeltaTime and 2 * the averaged - // frame update. Since smooth dampening is most useful for Rigidbody motion, the physics update - // frequency is roughly 60hz (59.x?) which 2x that value as frequency is typically close to 32-33ms. - // Look within the Interpolator.Update for smooth dampening to better understand the above. - var maxDeltaTime = (1.666667f * m_CachedNetworkManager.ServerTime.FixedDeltaTime); + // Get the tick latency (ticks ago) as time (in the past) to process state updates in the queue. + var tickLatencyAsTime = timeSystem.TimeTicksAgo(tickLatency).Time; + +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + // If using rigid body for motion, then we need to increment + // our tick latency based on the number of times FixedUpdate + // is executed. + if (m_UseRigidbodyForMotion) + { + tickLatencyAsTime += m_FixedTimeFrameDelta; + currentTime += m_FixedTimeFrameDelta; + } +#endif + + // Smooth dampening and extrapolation specific: + // We clamp between the tick rate frequency and the tick latency x tick rate frequency + var minDeltaTime = timeSystem.FixedDeltaTimeAsDouble; + + // Maximum delta time is the maximum time we will lerp between values. If the time exceeds this due to extreme + // latency then the value's interpolation rate will be accelerated to reach the goal and continue interpolating + // the next state updates. + var maxDeltaTime = tickLatency * minDeltaTime; // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) { - if (PositionInterpolationType == InterpolationTypes.Lerp) + if (PositionLerpSmoothing) { m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + m_PositionInterpolator.LerpSmoothEnabled = PositionLerpSmoothing; + + // If either of these two position interpolation related values have changed, then reset the current state being interpolated. + if (m_PreviousPositionInterpolationType != PositionInterpolationType || m_PreviousPositionLerpSmoothing != PositionLerpSmoothing) + { + m_PreviousPositionInterpolationType = PositionInterpolationType; + m_PreviousPositionLerpSmoothing = PositionLerpSmoothing; + m_PositionInterpolator.ResetCurrentState(); + } + + if (PositionInterpolationType == InterpolationTypes.LegacyLerp) + { + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, + PositionInterpolationType == InterpolationTypes.Lerp); } } if (SynchronizeRotation) { - if (RotationInterpolationType == InterpolationTypes.Lerp) + if (RotationLerpSmoothing) { m_RotationInterpolator.MaximumInterpolationTime = RotationMaxInterpolationTime; - // When using half precision Lerp towards the target rotation. - // When using full precision Slerp towards the target rotation. - /// - m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + + m_RotationInterpolator.LerpSmoothEnabled = RotationLerpSmoothing; + + // If either of these two rotation interpolation related values have changed, then reset the current state being interpolated. + if (m_PreviousRotationInterpolationType != RotationInterpolationType || m_PreviousRotationLerpSmoothing != RotationLerpSmoothing) + { + m_PreviousRotationInterpolationType = RotationInterpolationType; + m_PreviousRotationLerpSmoothing = RotationLerpSmoothing; + m_RotationInterpolator.ResetCurrentState(); + } + // When using half precision Lerp towards the target rotation. + // When using full precision Slerp towards the target rotation. + /// + m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; + if (RotationInterpolationType == InterpolationTypes.LegacyLerp) + { + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, + RotationInterpolationType == InterpolationTypes.Lerp); } } if (SynchronizeScale) { - if (ScaleInterpolationType == InterpolationTypes.Lerp) + if (ScaleLerpSmoothing) { m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + + m_ScaleInterpolator.LerpSmoothEnabled = ScaleLerpSmoothing; + + // If either of these two rotation interpolation related values have changed, then reset the current state being interpolated. + if (m_PreviousScaleInterpolationType != ScaleInterpolationType || m_PreviousScaleLerpSmoothing != ScaleLerpSmoothing) + { + m_PreviousScaleInterpolationType = ScaleInterpolationType; + m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; + m_ScaleInterpolator.ResetCurrentState(); + } + + if (ScaleInterpolationType == InterpolationTypes.LegacyLerp) + { + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, + ScaleInterpolationType == InterpolationTypes.Lerp); } } + +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + if (GatherStats) + { + if (!m_PositionInterpolator.InterpolateState.Target.HasValue) + { + return; + } + var posStats = new NTPositionStats() + { + FrameCount = m_FrameCount, + FixedFrameCount = m_FixedFrameCount, + FixedUpdatesPerFrameCount = m_FixedUpdatesPerFrameCount, + TimeSynchCount = m_CachedNetworkManager.NetworkTimeSystem.SyncCount, + Tick = timeSystem.Tick, + Time = timeSystem.Time, + TicksAgoTime = tickLatencyAsTime, + BufferCount = m_PositionInterpolator.m_BufferQueue.Count, + TickMeasured = (int)Math.Round(m_PositionInterpolator.InterpolateState.Target.Value.TimeSent / timeSystem.FixedDeltaTimeAsDouble, MidpointRounding.AwayFromZero), + LerpT = m_PositionInterpolator.InterpolateState.LerpT, + DeltaTime = m_PositionInterpolator.InterpolateState.DeltaTime, + MaxDeltaTime = m_PositionInterpolator.InterpolateState.MaxDeltaTime, + TimeSent = m_PositionInterpolator.InterpolateState.Target.Value.TimeSent, + TimeToTargetValue = m_PositionInterpolator.InterpolateState.TimeToTargetValue, + PreviousValue = m_PositionInterpolator.InterpolateState.PreviousValue, + NextValue = m_PositionInterpolator.InterpolateState.NextValue, + CurrentValue = m_PositionInterpolator.InterpolateState.CurrentValue, + TargetValue = m_PositionInterpolator.InterpolateState.Target.Value.Item, + Buffer = new List(), + }; + + foreach (var entry in m_PositionInterpolator.m_BufferQueue) + { + posStats.Buffer.Add(new BufferEntry() + { + TimeSent = entry.TimeSent, + Position = entry.Item, + }); + } + if (!PositionStats.ContainsKey(posStats.TickMeasured)) + { + PositionStats.Add(posStats.TickMeasured, new List()); + } + PositionStats[posStats.TickMeasured].Add(posStats); + } +#endif } /// @@ -3936,12 +4266,47 @@ public virtual void OnUpdate() UpdateInterpolation(); } - // Apply the current authoritative state ApplyAuthoritativeState(); } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + private int m_FrameCount = 0; + private int m_FixedFrameCount = 0; + private int m_FixedUpdatesPerFrameCount = 0; +#endif + + // This is used during fixed update in case there are multiple fixed update passes without any frame pass. + private float m_FixedTimeFrameDelta; + + // The fixed time (static time step) value for the current frame (in the event it is changed at runtime). + private float m_DeltaFixedUpdateCached; + + /// + /// Resets the total fixed update time passed. + /// This handles dealing with multiple passes within FixedUpdate and interpolation. + /// + internal void ResetFixedTimeDelta() + { + // If not spawned or this instance has authority, exit early + if (!m_UseRigidbodyForMotion || !IsSpawned || CanCommitToTransform) + { + return; + } + + // Get the current fixed delta time (used in fixed upate) + m_DeltaFixedUpdateCached = m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime; + // Reset the total fixed update time (increased each time physics invokes FixedUpdate within the same frame) + m_FixedTimeFrameDelta = 0.0f; +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + m_FrameCount++; + m_FixedUpdatesPerFrameCount = 0; +#endif + } + /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3956,6 +4321,11 @@ public virtual void OnFixedUpdate() m_NetworkRigidbodyInternal.WakeIfSleeping(); +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + m_FixedFrameCount++; + m_FixedUpdatesPerFrameCount++; +#endif // Update interpolation when enabled if (Interpolate) @@ -3965,6 +4335,10 @@ public virtual void OnFixedUpdate() // Apply the current authoritative state ApplyAuthoritativeState(); + + // Increment the time passed based on our current fixed update rate in case + // FixedUpdate is invoked more than once within a single frame. + m_FixedTimeFrameDelta += m_DeltaFixedUpdateCached; } #endif @@ -4118,12 +4492,23 @@ private void UpdateTransformState() #region NETWORK TICK REGISTRATOIN AND HANDLING private static Dictionary s_NetworkTickRegistration = new Dictionary(); - + /// + /// Adjusts the over-all tick offset (i.e. how many ticks ago) and how wide of a maximum delta time will be used for the + /// various . + /// + /// + /// Note: You can adjust this value during runtime. Increasing this value will set non-authority instances that much further + /// behind the authority instance but will increase the number of state updates to be processed. Increasing this can be useful + /// under higher latency conditions.
+ /// The default value is 1 tick (plus the tick latency). When running on a local network, reducing this to 0 is recommended.
+ /// + ///
+ public static int InterpolationBufferTickOffset = 0; internal static float GetTickLatency(NetworkManager networkManager) { if (networkManager.IsListening) { - return (float)(networkManager.NetworkTimeSystem.TickLatency + networkManager.LocalTime.TickOffset); + return (float)(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset + networkManager.LocalTime.TickOffset); } return 0; } @@ -4145,7 +4530,7 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager) { if (networkManager.IsListening) { - return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency).Time; + return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time; } return 0f; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index a35fcfec75..eed0bc497c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -333,6 +333,25 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) MessageManager.CleanupDisconnectedClients(); AnticipationSystem.ProcessReanticipation(); +#if COM_UNITY_MODULES_PHYSICS + foreach (var networkObjectEntry in NetworkTransformFixedUpdate) + { + // if not active or not spawned then skip + if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) + { + continue; + } + + foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) + { + // only update if enabled + if (networkTransformEntry.enabled) + { + networkTransformEntry.ResetFixedTimeDelta(); + } + } + } +#endif } break; #if COM_UNITY_MODULES_PHYSICS diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs index 97b0fcc5e7..e3ab1dfe32 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -26,6 +26,7 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, Tick); + networkManager.NetworkTimeSystem.SyncCount++; networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(context.SenderId) / 1000d); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index c3fc9e8b67..652b59cc09 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -82,10 +82,16 @@ public class NetworkTimeSystem /// The averaged latency in network ticks between a client and server. ///
/// - /// For a distributed authority network topology, this latency is between - /// the client and the distributed authority service instance. + /// For a distributed authority network topology, this latency is between the client and the + /// distributed authority service instance.
+ /// Note: uses this value plus an additional global + /// offset when interpolation + /// is enabled.
+ /// To see the current tick latency:
+ /// -
+ /// -
///
- public int TickLatency = 2; + public int TickLatency = 1; internal double LastSyncedServerTimeSec { get; private set; } internal double LastSyncedRttSec { get; private set; } @@ -257,7 +263,7 @@ public void Reset(double serverTimeSec, double rttSec) Sync(serverTimeSec, rttSec); Advance(0); } - + internal int SyncCount; /// /// Synchronizes the time system with up-to-date network statistics but does not change any time values or advance the time. /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index a6f0f3a4cb..b518f26583 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -38,6 +38,7 @@ public enum SceneManagementState } private StringBuilder m_InternalErrorLog = new StringBuilder(); + internal StringBuilder VerboseDebugLog = new StringBuilder(); /// /// Registered list of all NetworkObjects spawned. @@ -257,6 +258,7 @@ protected void VerboseDebug(string msg) { if (m_EnableVerboseDebug) { + VerboseDebugLog.AppendLine(msg); Debug.Log(msg); } } @@ -345,6 +347,7 @@ protected virtual void OnInlineSetup() [UnitySetUp] public IEnumerator SetUp() { + VerboseDebugLog.Clear(); VerboseDebug($"Entering {nameof(SetUp)}"); NetcodeLogAssert = new NetcodeLogAssert(); if (m_EnableTimeTravel) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 703ed7433e..7d9635dd76 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -631,14 +631,13 @@ protected bool PositionsMatchesValue(Vector3 positionToMatch) var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position; var auhtorityIsEqual = Approximately(authorityPosition, positionToMatch); var nonauthorityIsEqual = Approximately(nonAuthorityPosition, positionToMatch); - if (!auhtorityIsEqual) { - VerboseDebug($"Authority position {authorityPosition} != position to match: {positionToMatch}!"); + VerboseDebug($"Authority ({m_AuthoritativeTransform.name}) position {authorityPosition} != position to match: {positionToMatch}!"); } if (!nonauthorityIsEqual) { - VerboseDebug($"NonAuthority position {nonAuthorityPosition} != position to match: {positionToMatch}!"); + VerboseDebug($"NonAuthority ({m_NonAuthoritativeTransform.name}) position {nonAuthorityPosition} != position to match: {positionToMatch}!"); } return auhtorityIsEqual && nonauthorityIsEqual; } @@ -782,6 +781,7 @@ internal class NetworkTransformTestComponent : NetworkTransform protected override void OnAuthorityPushTransformState(ref NetworkTransformState networkTransformState) { + Debug.Log($"[Auth]{name} State Pushed."); StatePushed = true; AuthorityLastSentState = networkTransformState; AuthorityPushedTransformState?.Invoke(ref networkTransformState); @@ -792,10 +792,21 @@ protected override void OnAuthorityPushTransformState(ref NetworkTransformState public bool StateUpdated { get; internal set; } protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState) { + Debug.Log($"[Non-Auth]{name} State Updated."); StateUpdated = true; base.OnNetworkTransformStateUpdated(ref oldState, ref newState); } + protected string GetVector3Values(ref Vector3 vector3) + { + return $"({vector3.x:F6},{vector3.y:F6},{vector3.z:F6})"; + } + + protected string GetVector3Values(Vector3 vector3) + { + return GetVector3Values(ref vector3); + } + protected override bool OnIsServerAuthoritative() { return ServerAuthority; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index efb1ce1244..92c47522c1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -4,12 +4,20 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] internal class NetworkTransformGeneral : NetworkTransformBase { + public enum SmoothLerpSettings + { + SmoothLerp, + NormalLerp + } + public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority, NetworkTransform.InterpolationTypes interpolationType) : base(testWithHost, authority, RotationCompression.None, Rotation.Euler, Precision.Full) { @@ -279,39 +287,42 @@ public void TestMultipleExplicitSetStates([Values] Interpolation interpolation) /// This also tests that the original server authoritative model with client-owner driven NetworkTransforms is preserved. /// [Test] - public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation) + public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation, [Values] SmoothLerpSettings smoothLerp) { var interpolate = interpolation == Interpolation.EnableInterpolate; + var usingSmoothLerp = (smoothLerp == SmoothLerpSettings.SmoothLerp) && interpolate; + var waitForDelay = usingSmoothLerp ? 1000 : 500; + m_NonAuthoritativeTransform.PositionLerpSmoothing = usingSmoothLerp; + m_NonAuthoritativeTransform.RotationLerpSmoothing = usingSmoothLerp; + m_NonAuthoritativeTransform.ScaleLerpSmoothing = usingSmoothLerp; + m_AuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; m_EnableVerboseDebug = true; - - m_AuthoritativeTransform.Teleport(Vector3.zero, Quaternion.identity, Vector3.one); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(Vector3.zero, Quaternion.identity.eulerAngles, Vector3.one), 800); - Assert.True(success, $"Timed out waiting for initialization to be applied!"); - + VerboseDebug($"Target Frame Rate: {Application.targetFrameRate}"); // Test one parameter at a time first - var newPosition = new Vector3(55f, 35f, 65f); + var newPosition = usingSmoothLerp ? new Vector3(15f, -12f, 10f) : new Vector3(55f, -24f, 20f); var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); - Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!"); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), waitForDelay); + Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 800); - Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!"); - Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); - Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); + success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), waitForDelay); + Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 800); - Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!"); - Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); - Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), waitForDelay); + Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {VerboseDebugLog}"); // Test all parameters at once newPosition = new Vector3(-10f, 95f, -25f); @@ -319,14 +330,14 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati newScale = new Vector3(0.5f, 0.5f, 0.5f); m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 800); - Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!"); - Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); - Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); - Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); - Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); - Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); - Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), waitForDelay); + Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!\n {VerboseDebugLog}"); + Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), $"Authoritative position does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), $"Non-Authoritative position does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {VerboseDebugLog}"); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 6418e0f23f..d4f22c11c5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -10,9 +10,16 @@ namespace Unity.Netcode.RuntimeTests /// server and host operating modes and will test both authoritative /// models for each operating mode. /// + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -26,9 +33,16 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -41,9 +55,16 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -471,7 +492,7 @@ public void ParentedNetworkTransformTest([Values] Interpolation interpolation, [ /// delta update, and it runs through 8 delta updates per unique test. /// [Test] - public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState, [Values] Axis axis) + public void MultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState, [Values] Axis axis) { m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; bool axisX = axis == Axis.X || axis == Axis.XY || axis == Axis.XZ || axis == Axis.XYZ; @@ -486,6 +507,30 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test // when interpolation is enabled. m_AuthoritativeTransform.Interpolate = axisCount == 3 ? true : false; + // Lerp smoothing skews values based on our tests and how we had to originally adjust for the way we handled the original Lerp approach and how that + // consumed state updates from the buffer. + // With the two new interpolation types, they will process until close to the final value before moving on to the next. + // Lerp--> Will skip to next state before finishing the current state (i.e. loss of precision but arrives to the final value at the end of multiple updates faster) + // LerpAhead & SmoothDampening --> + // Will not skip to the next state update until approximately at the end of the current state (higher precision longer time to final value) + // How this impacts this test: + // It was re-written to use TimeTravel which has a limit of 60 update iterations per "WaitforCondition" which if you are interpolating between two large values + // it can take a few more iterations with lerp smoothing enabled. Lerp smoothing is purely a visual thing and will eventually end up at its final destination + // upon processing the last state update. However, this test should be only to verify the functionality of the actual lerping between values without the added + // delay of smoothing the final result. So, instead of having one timeout value for the two new interpolation types and the default for the original I am opting + // for the disabling of lerp smoothing while this particular test runs as it really is only validating that each interpolator type will interpolate to the right + // value within a given period of time which is simulated using the time travel approach. + // With smooth lerping enabled, the two new interpolation types will come very close to the correct value but will not reach the 2nd or 3rd pass values set because + // this test uses the adjusted approximation checks that prematurely determines the target values (position, rotation, and scale) have been reached and as such + // sends a new state update that will sit in the buffer for 3-4 frames before the two new interpolation types are done with the current state update. This will + // eventually lead to a time deficit that will offset the processing of the next state update such that the default time travel timeout (60 updates) will timeout + // and the test will fail. This only happens with 3 axis since that is the only time interpolation was enabled for this particular test. + // As such, just disabling smooth lerping for all 3 seemed like the better approach as the maximum interpolation time out period for smooth lerping is now + // adjustable by users (i.e. they can adjust how much lerp smoothing is applied based on their project's needs). + m_NonAuthoritativeTransform.PositionLerpSmoothing = false; + m_NonAuthoritativeTransform.RotationLerpSmoothing = false; + m_NonAuthoritativeTransform.ScaleLerpSmoothing = false; + m_CurrentAxis = axis; m_AuthoritativeTransform.SyncPositionX = axisX; @@ -520,12 +565,18 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test var scale = scaleStart; var success = false; - m_AuthoritativeTransform.StatePushed = false; - // Wait for the deltas to be pushed - WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed); - // Allow the precision settings to propagate first as changing precision - // causes a teleport event to occur - TimeTravelAdvanceTick(); + // The default is interpolate, so we only need to check for the updated state when + // we turn off interpolation. + if (!m_AuthoritativeTransform.Interpolate) + { + // Reset our state updated and state pushed + m_NonAuthoritativeTransform.StateUpdated = false; + m_AuthoritativeTransform.StatePushed = false; + // Wait for both authority and non-authority to update their respective flags so we know the change to interpolation has been received. + success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated); + Assert.True(success, "Failed to wait for interpolation changed state update!"); + } + var iterations = axisCount == 3 ? k_PositionRotationScaleIterations3Axis : k_PositionRotationScaleIterations; // Move and rotate within the same tick, validate the non-authoritative instance updates @@ -574,6 +625,7 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test if (!success) { m_EnableVerboseDebug = true; + VerboseDebug($"Failed on iteration: {i}"); success = PositionRotationScaleMatches(); m_EnableVerboseDebug = false; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 4e0cea8e53..840204d4a5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -14,7 +14,7 @@ internal class TransformInterpolationObject : NetworkTransform public static bool TestComplete = false; // Set the minimum threshold which we will use as our margin of error #if UNITY_EDITOR - public const float MinThreshold = 0.005f; + public const float MinThreshold = 0.00555555f; #else // Add additional room for error on console tests public const float MinThreshold = 0.009999f; diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 60cbf38082..dc5b2b4e9d 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -34,6 +34,21 @@ namespace TestProject.RuntimeTests [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + + // Lerp, extrapolate, and blend pass + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + // Smooth dampening interpolation pass [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] @@ -106,7 +121,7 @@ public NestedNetworkTransformTests(NetworkTransform.InterpolationTypes interpola } public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) : - this(NetworkTransform.InterpolationTypes.Lerp, interpolation, precision, authoritativeModel, nestedTickSynchronization) + this(NetworkTransform.InterpolationTypes.LegacyLerp, interpolation, precision, authoritativeModel, nestedTickSynchronization) { } public NestedNetworkTransformTests() From bb0566c77d2095bf5d35b76c95bbfa0f212625d2 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 4 Apr 2025 02:39:50 -0500 Subject: [PATCH 216/236] fix: LegacyLerp was using ticklatency as opposed to legacy ticksAgo (#3381) This update resolves the issue where the legacy lerp result was not yielding the same result as the original lerp. This update also resolves a secondary issue discovered by the services group where the final interpolation (i.e. last measurement in the queue) was not using a high enough precision value when determining if it reached its final destination point. This update also includes some inlining additions along with a spelling mistake with the IsApproximately method. It also updates the XML API regarding Legacy lerp to let users know that it does not use the tick latency value when calculating the ticksAgo value. ## Changelog NA ## Testing and Documentation - No tests have been added. - Includes edits to existing API XML documentation. --- .../BufferedLinearInterpolator.cs | 52 +++++++++++++------ .../BufferedLinearInterpolatorFloat.cs | 2 +- .../BufferedLinearInterpolatorQuaternion.cs | 2 +- .../BufferedLinearInterpolatorVector3.cs | 2 +- .../Runtime/Components/NetworkTransform.cs | 19 +++++-- 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 538a6ba1a1..b07eca8250 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -14,9 +15,16 @@ public abstract class BufferedLinearInterpolator where T : struct // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so // that we don't have a very small buffer because of this. private const int k_BufferCountLimit = 100; - private const float k_AproximatePrecision = 0.0001f; + private const float k_ApproximateLowPrecision = 0.000001f; + private const float k_ApproximateHighPrecision = 1E-10f; private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float GetPrecision() + { + return m_BufferQueue.Count == 0 ? k_ApproximateHighPrecision : k_ApproximateLowPrecision; + } + #region Legacy notes // Buffer consumption scenarios // Perfect case consumption @@ -132,6 +140,7 @@ internal struct CurrentState public float CurrentDeltaTime => m_CurrentDeltaTime; public double FinalTimeToTarget => Math.Max(0.0, TimeToTargetValue - DeltaTime); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddDeltaTime(float deltaTime) { m_CurrentDeltaTime = deltaTime; @@ -139,6 +148,7 @@ public void AddDeltaTime(float deltaTime) LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetTimeToTarget(double timeToTarget) { LerpT = 0.0f; @@ -146,6 +156,7 @@ public void SetTimeToTarget(double timeToTarget) TimeToTargetValue = timeToTarget; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TargetTimeAproximatelyReached() { if (!Target.HasValue) @@ -237,6 +248,7 @@ public void ResetTo(T targetValue, double serverTime) InternalReset(targetValue, serverTime); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void InternalReset(T targetValue, double serverTime, bool addMeasurement = true) { m_RateOfChange = default; @@ -271,7 +283,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double { if (!InterpolateState.TargetReached) { - InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } return; } @@ -291,7 +303,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double potentialItemNeedsProcessing = ((potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent); if (!InterpolateState.TargetReached) { - InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } } @@ -424,6 +436,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime /// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern. /// /// + /// private void TryConsumeFromBuffer(double renderTime, double serverTime) { if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime)) @@ -433,14 +446,16 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. + // to consume. (just a safety check but this scenario should never happen based on the below legacy approach of + // consuming until the most current state) if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) { break; } - if ((potentialItem.TimeSent <= serverTime) && - (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + // Continue to processing until we reach the most current state + if ((potentialItem.TimeSent <= serverTime) && // Inverted logic (below) from original since we have to go from past to present + (!InterpolateState.Target.HasValue || potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent)) { if (m_BufferQueue.TryDequeue(out BufferedItem target)) { @@ -449,6 +464,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) InterpolateState.Target = target; alreadyHasBufferItem = true; InterpolateState.NextValue = InterpolateState.CurrentValue; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; InterpolateState.StartTime = target.TimeSent; InterpolateState.EndTime = target.TimeSent; } @@ -458,19 +474,15 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { alreadyHasBufferItem = true; InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.NextValue = InterpolateState.CurrentValue; + InterpolateState.PreviousValue = InterpolateState.NextValue; InterpolateState.TargetReached = false; } InterpolateState.EndTime = target.TimeSent; - InterpolateState.Target = target; InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; + InterpolateState.Target = target; } } } - else - { - break; - } if (!InterpolateState.Target.HasValue) { @@ -505,19 +517,20 @@ public T Update(float deltaTime, double renderTime, double serverTime) InterpolateState.LerpT = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f); } - var target = Interpolate(InterpolateState.NextValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + InterpolateState.NextValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); if (LerpSmoothEnabled) { // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, deltaTime / MaximumInterpolationTime); } else { - InterpolateState.CurrentValue = target; + InterpolateState.CurrentValue = InterpolateState.NextValue; } + // Determine if we have reached our target - InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. if (m_BufferQueue.Count == 0 && InterpolateState.TargetReached) @@ -601,6 +614,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) /// Gets latest value from the interpolator. This is updated every update as time goes by. ///
/// The current interpolated value of type 'T' + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetInterpolatedValue() { return InterpolateState.CurrentValue; @@ -638,6 +652,7 @@ public T GetInterpolatedValue() /// The increasing delta time from when start to finish. /// Maximum rate of change per pass. /// The smoothed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) { return target; @@ -653,7 +668,8 @@ private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, /// Second value of type . /// The precision of the aproximation. /// true if the two values are aproximately the same and false if they are not - private protected virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private protected virtual bool IsApproximately(T first, T second, float precision = k_ApproximateLowPrecision) { return false; } @@ -665,6 +681,7 @@ private protected virtual bool IsAproximately(T first, T second, float precision /// The item to convert. /// local or world space (true or false). /// The converted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal virtual T OnConvertTransformSpace(Transform transform, T item, bool inLocalSpace) { return default; @@ -675,6 +692,7 @@ protected internal virtual T OnConvertTransformSpace(Transform transform, T item ///
/// The transform that the is associated with. /// Whether the is now being tracked in local or world spaced. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) { var count = m_BufferQueue.Count; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs index 77583468a7..654d64917a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -25,7 +25,7 @@ protected override float Interpolate(float start, float end, float time) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected override bool IsAproximately(float first, float second, float precision = 1E-07F) + private protected override bool IsApproximately(float first, float second, float precision = 1E-06F) { return Mathf.Approximately(first, second); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 5b8033a977..628498ada0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -66,7 +66,7 @@ private protected override Quaternion SmoothDamp(Quaternion current, Quaternion /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) + private protected override bool IsApproximately(Quaternion first, Quaternion second, float precision = 1E-06F) { return Mathf.Abs(first.x - second.x) <= precision && Mathf.Abs(first.y - second.y) <= precision && diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index aa5a739683..ce836bc199 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -59,7 +59,7 @@ protected internal override Vector3 OnConvertTransformSpace(Transform transform, /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) + private protected override bool IsApproximately(Vector3 first, Vector3 second, float precision = 1E-06F) { return Math.Round(Mathf.Abs(first.x - second.x), 2) <= precision && Math.Round(Mathf.Abs(first.y - second.y), 2) <= precision && diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index aeb9687e7b..92943ee0b5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -968,6 +968,8 @@ public enum InterpolationTypes /// The first phase lerps from the previous state update value to the next state update value. /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. /// + /// !!! NOTE !!!
+ /// The legacy lerp interpolation type does not use to determine the buffer depth. This is to preserve the same interpolation results when lerp smoothing is enabled.
///
/// /// For more information:
@@ -4085,6 +4087,17 @@ private void UpdateInterpolation() } } + // Note: This is for the legacy lerp type in order to maintain the same end result for any games under development that have tuned their + // project's to match the legacy lerp's end result. This will not allow changes + var cachedRenderTime = 0.0; + if (PositionInterpolationType == InterpolationTypes.LegacyLerp || RotationInterpolationType == InterpolationTypes.LegacyLerp || ScaleInterpolationType == InterpolationTypes.LegacyLerp) + { + // Since InterpolationBufferTickOffset defaults to zero, this should not impact exist projects but still provides users with the ability to tweak + // their ticks ago time. + var ticksAgo = (!IsServerAuthoritative() && !IsServer ? 2 : 1) + InterpolationBufferTickOffset; + cachedRenderTime = timeSystem.TimeTicksAgo(ticksAgo).Time; + } + // Get the tick latency (ticks ago) as time (in the past) to process state updates in the queue. var tickLatencyAsTime = timeSystem.TimeTicksAgo(tickLatency).Time; @@ -4127,7 +4140,7 @@ private void UpdateInterpolation() if (PositionInterpolationType == InterpolationTypes.LegacyLerp) { - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); + m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime); } else { @@ -4158,7 +4171,7 @@ private void UpdateInterpolation() m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; if (RotationInterpolationType == InterpolationTypes.LegacyLerp) { - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); + m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime); } else { @@ -4186,7 +4199,7 @@ private void UpdateInterpolation() if (ScaleInterpolationType == InterpolationTypes.LegacyLerp) { - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); + m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime); } else { From 53b94ca902a10368806cf1e0b2965f707d7c35c1 Mon Sep 17 00:00:00 2001 From: Dominick Date: Mon, 7 Apr 2025 12:00:26 -0600 Subject: [PATCH 217/236] feat: session state token (#3228) https://jira.unity3d.com/browse/MTT-10043 https://docs.google.com/document/d/1st5PoxRa7rg5JsLW0pKbVAgaqBP1QdUCx2gFlHXEicg/edit?tab=t.0 Adding a SessionStateToken to the ConnectionApproved message and incrementing the version. This is safe without implementing the feature as the service does not need to make any assumptions about what the client does with this information. ## Changelog - Added: Session State Token into the ConnectionApproved message ## Testing and Documentation - Tested using E2E tests and existing tests ## Related PR https://github.com/Unity-Technologies/mps-common-multiplayer-backend/pull/976 --- .../Runtime/Configuration/SessionConfig.cs | 3 ++- .../Messages/ConnectionApprovedMessage.cs | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs index 671ae74811..9a4ed0a931 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/SessionConfig.cs @@ -8,9 +8,10 @@ internal class SessionConfig public const uint NoFeatureCompatibility = 0; public const uint BypassFeatureCompatible = 1; public const uint ServerDistributionCompatible = 2; + public const uint SessionStateToken = 3; // The most current session version (!!!!set this when you increment!!!!!) - public static uint PackageSessionVersion => ServerDistributionCompatible; + public static uint PackageSessionVersion => SessionStateToken; internal uint SessionVersion; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index cb1de9356f..ab46a4465f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -9,6 +9,7 @@ internal struct ServiceConfig : INetworkSerializable public bool IsRestoredSession; public ulong CurrentSessionOwner; public bool ServerRedistribution; + public ulong SessionStateToken; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { @@ -22,6 +23,11 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { serializer.SerializeValue(ref ServerRedistribution); } + + if (SessionVersion >= SessionConfig.SessionStateToken) + { + serializer.SerializeValue(ref SessionStateToken); + } } else { @@ -37,6 +43,15 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { ServerRedistribution = false; } + + if (SessionVersion >= SessionConfig.SessionStateToken) + { + serializer.SerializeValue(ref SessionStateToken); + } + else + { + SessionStateToken = 0; + } } } } From 78cad423085fd2d866d4073d6bb19c4efa43e53b Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 8 Apr 2025 15:08:00 -0400 Subject: [PATCH 218/236] fix: Don't accept invalid connections in UnityTransport.Send (#3382) This avoids the error message reported in [MTTB-1143](https://jira.unity3d.com/browse/MTTB-1143). The `UnityTransport.Send` method would happily accept invalid or stale connections, which would lead to the allocation of a `BatchedSendQueue` structure and to an error message when the batched sends are passed on to the driver. In MTTB-1143 we'd get a send on client ID 0, but that's not a valid client ID in `UnityTransport`. My guess is that there's something somewhere (possibly in Boss Room) triggering a send to the server after the connection has closed (which reverts `UnityTransport.ServerClientId` to its default value of 0). Of course, ideally there'd never be such a send-after-disconnect and that's what should be ultimately addressed. But the bug is quite hard to reproduce, so as a stopgap until we have something better I'm making a fix that only avoids the last and more user-visible consequences of the issue. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Transports/UTP/UnityTransport.cs | 7 ++++++- .../Runtime/Transports/UnityTransportTests.cs | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 74718c95b9..480152dc66 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed an issue in `UnityTransport` where the transport would accept sends on invalid connections, leading to a useless memory allocation and confusing error message. (#3382) - Fixed issue where the time delta that interpolators used would not be properly updated during multiple fixed update invocations within the same player loop frame. (#3355) - Fixed issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. (#3350) - Fixed issue when using a distributed authority network topology and scene management was disabled clients would not be able to spawn any new network prefab instances until synchronization was complete. (#3350) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index 3bd74f7aab..da3b797aea 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1347,8 +1347,13 @@ public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegme /// The delivery type (QoS) to send data with public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) { - var pipeline = SelectSendPipeline(networkDelivery); + var connection = ParseClientId(clientId); + if (!m_Driver.IsCreated || m_Driver.GetConnectionState(connection) != NetworkConnection.State.Connected) + { + return; + } + var pipeline = SelectSendPipeline(networkDelivery); if (pipeline != m_ReliableSequencedPipeline && payload.Count > m_MaxPayloadSize) { Debug.LogError($"Unreliable payload of size {payload.Count} larger than configured 'Max Payload Size' ({m_MaxPayloadSize})."); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index 4cd33fcc9b..ba68c4147f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -483,5 +483,22 @@ public IEnumerator DoesNotActAfterShutdown([Values] AfterShutdownAction afterShu yield return EnsureNoNetworkEvent(m_Client1Events); } } + + [UnityTest] + public IEnumerator DoesNotAttemptToSendOnInvalidConnections() + { + InitializeTransport(out m_Server, out m_ServerEvents); + InitializeTransport(out m_Client1, out m_Client1Events); + + m_Server.StartServer(); + m_Client1.StartClient(); + + yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); + + var data = new ArraySegment(new byte[42]); + m_Server.Send(0, data, NetworkDelivery.Reliable); + + yield return EnsureNoNetworkEvent(m_Client1Events); + } } } From 23412a0a5d3aeddbffaf9480644b5a1d81f45ddb Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Tue, 8 Apr 2025 16:52:55 -0400 Subject: [PATCH 219/236] refactor: Minor cleanups in Unity Transport (#3377) Just a few minor cleanups in Unity Transport: - Remove the internal `NetworkDriver` accessor. There's a public `GetNetworkDriver` nowadays that can be used for the same purpose. - Add a `using` directive for transport errors which avoids using the full (and long) type name. - Remove the internal "state" member. The same information can be gleaned by looking at the driver's state and the value of `m_ServerClientId`. Plus, the `Listening` state did not have the same meaning as it does in NGO itself, which could lead to confusion. - Simplify `ErrorUtilities` to avoid the useless constants and only providing strings for errors users can actually do something about. Also moved it to the bottom of the file. The top is too prime real estate for such a minor utility. :P --- .../Runtime/Transports/UTP/UnityTransport.cs | 171 +++++++----------- .../Editor/Transports/UnityTransportTests.cs | 2 +- .../Runtime/Metrics/PacketLossMetricsTests.cs | 4 +- .../Runtime/Transports/UnityTransportTests.cs | 4 +- 4 files changed, 75 insertions(+), 106 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index da3b797aea..210d7f660b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -16,8 +16,10 @@ using Unity.Networking.Transport.TLS; using Unity.Networking.Transport.Utilities; using UnityEngine; -using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent; -using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent; + +using NetcodeEvent = Unity.Netcode.NetworkEvent; +using TransportError = Unity.Networking.Transport.Error.StatusCode; +using TransportEvent = Unity.Networking.Transport.NetworkEvent.Type; namespace Unity.Netcode.Transports.UTP { @@ -42,56 +44,6 @@ void CreateDriver( out NetworkPipeline reliableSequencedPipeline); } - /// - /// Helper utility class to convert error codes to human readable error messages. - /// - public static class ErrorUtilities - { - private static readonly FixedString128Bytes k_NetworkSuccess = "Success"; - private static readonly FixedString128Bytes k_NetworkIdMismatch = "Invalid connection ID {0}."; - private static readonly FixedString128Bytes k_NetworkVersionMismatch = "Connection ID is invalid. Likely caused by sending on stale connection {0}."; - private static readonly FixedString128Bytes k_NetworkStateMismatch = "Connection state is invalid. Likely caused by sending on connection {0} which is stale or still connecting."; - private static readonly FixedString128Bytes k_NetworkPacketOverflow = "Packet is too large to be allocated by the transport."; - private static readonly FixedString128Bytes k_NetworkSendQueueFull = "Unable to queue packet in the transport. Likely caused by send queue size ('Max Send Queue Size') being too small."; - - /// - /// Convert a UTP error code to human-readable error message. - /// - /// UTP error code. - /// ID of the connection on which the error occurred. - /// Human-readable error message. - public static string ErrorToString(Networking.Transport.Error.StatusCode error, ulong connectionId) - { - return ErrorToString((int)error, connectionId); - } - - internal static string ErrorToString(int error, ulong connectionId) - { - return ErrorToFixedString(error, connectionId).ToString(); - } - - internal static FixedString128Bytes ErrorToFixedString(int error, ulong connectionId) - { - switch ((Networking.Transport.Error.StatusCode)error) - { - case Networking.Transport.Error.StatusCode.Success: - return k_NetworkSuccess; - case Networking.Transport.Error.StatusCode.NetworkIdMismatch: - return FixedString.Format(k_NetworkIdMismatch, connectionId); - case Networking.Transport.Error.StatusCode.NetworkVersionMismatch: - return FixedString.Format(k_NetworkVersionMismatch, connectionId); - case Networking.Transport.Error.StatusCode.NetworkStateMismatch: - return FixedString.Format(k_NetworkStateMismatch, connectionId); - case Networking.Transport.Error.StatusCode.NetworkPacketOverflow: - return k_NetworkPacketOverflow; - case Networking.Transport.Error.StatusCode.NetworkSendQueueFull: - return k_NetworkSendQueueFull; - default: - return FixedString.Format("Unknown error code {0}.", error); - } - } - } - /// /// The Netcode for GameObjects NetworkTransport for UnityTransport. /// Note: This is highly recommended to use over UNet. @@ -114,13 +66,6 @@ public enum ProtocolType RelayUnityTransport, } - private enum State - { - Disconnected, - Listening, - Connected, - } - /// /// The default maximum (receive) packet queue size /// @@ -421,17 +366,16 @@ private struct PacketLossCache internal static event Action TransportInitialized; internal static event Action TransportDisposed; - internal NetworkDriver NetworkDriver => m_Driver; /// - /// Provides access to the for this instance. + /// Provides access to the for this instance. /// protected NetworkDriver m_Driver; /// - /// Gets a reference to the . + /// Gets a reference to the . /// - /// ref + /// ref public ref NetworkDriver GetNetworkDriver() { return ref m_Driver; @@ -455,7 +399,6 @@ public NetworkEndpoint GetLocalEndpoint() private PacketLossCache m_PacketLossCache = new PacketLossCache(); - private State m_State = State.Disconnected; private NetworkSettings m_NetworkSettings; private ulong m_ServerClientId; @@ -501,7 +444,7 @@ private void InitDriver() out m_UnreliableSequencedFragmentedPipeline, out m_ReliableSequencedPipeline); - TransportInitialized?.Invoke(GetInstanceID(), NetworkDriver); + TransportInitialized?.Invoke(GetInstanceID(), m_Driver); } private void DisposeInternals() @@ -583,8 +526,7 @@ private bool ClientBindAndConnect() return false; } - var serverConnection = Connect(serverEndpoint); - m_ServerClientId = ParseClientId(serverConnection); + Connect(serverEndpoint); return true; } @@ -624,7 +566,6 @@ private bool ServerBindAndListen(NetworkEndpoint endPoint) return false; } - m_State = State.Listening; return true; } @@ -776,9 +717,9 @@ public void Execute() while (!Queue.IsEmpty) { var result = Driver.BeginSend(pipeline, connection, out var writer); - if (result != (int)Networking.Transport.Error.StatusCode.Success) + if (result != (int)TransportError.Success) { - Debug.LogError($"Error sending message: {ErrorUtilities.ErrorToFixedString(result, clientId)}"); + Debug.LogError($"Send error on connection {clientId}: {ErrorUtilities.ErrorToFixedString(result)}"); return; } @@ -803,9 +744,9 @@ public void Execute() // and we'll retry sending them later). Otherwise log the error and remove the // message from the queue (we don't want to resend it again since we'll likely // just get the same error again). - if (result != (int)Networking.Transport.Error.StatusCode.NetworkSendQueueFull) + if (result != (int)TransportError.NetworkSendQueueFull) { - Debug.LogError($"Error sending the message: {ErrorUtilities.ErrorToFixedString(result, clientId)}"); + Debug.LogError($"Send error on connection {clientId}: {ErrorUtilities.ErrorToFixedString(result)}"); Queue.Consume(written); } @@ -849,7 +790,7 @@ private bool AcceptConnection() return false; } - InvokeOnTransportEvent(NetcodeNetworkEvent.Connect, + InvokeOnTransportEvent(NetcodeEvent.Connect, ParseClientId(connection), default, m_RealTimeProvider.RealTimeSinceStartup); @@ -887,7 +828,7 @@ private void ReceiveMessages(ulong clientId, NetworkPipeline pipeline, DataStrea break; } - InvokeOnTransportEvent(NetcodeNetworkEvent.Data, clientId, message, m_RealTimeProvider.RealTimeSinceStartup); + InvokeOnTransportEvent(NetcodeEvent.Data, clientId, message, m_RealTimeProvider.RealTimeSinceStartup); } } @@ -898,44 +839,38 @@ private bool ProcessEvent() switch (eventType) { - case TransportNetworkEvent.Type.Connect: + case TransportEvent.Connect: { - InvokeOnTransportEvent(NetcodeNetworkEvent.Connect, + InvokeOnTransportEvent(NetcodeEvent.Connect, clientId, default, m_RealTimeProvider.RealTimeSinceStartup); - m_State = State.Connected; + m_ServerClientId = clientId; return true; } - case TransportNetworkEvent.Type.Disconnect: + case TransportEvent.Disconnect: { - // Handle cases where we're a client receiving a Disconnect event. The - // meaning of the event depends on our current state. If we were connected - // then it means we got disconnected. If we were disconnected means that our - // connection attempt has failed. - if (m_State == State.Connected) - { - m_State = State.Disconnected; - m_ServerClientId = default; - } - else if (m_State == State.Disconnected) + // If we're a client and had not yet set the server client ID, it means + // our connection to the server failed to be established. Any other case + // means a clean disconnect that doesn't require logging. + if (!m_Driver.Listening && m_ServerClientId == default) { Debug.LogError("Failed to connect to server."); - m_ServerClientId = default; } + m_ServerClientId = default; m_ReliableReceiveQueues.Remove(clientId); ClearSendQueuesForClientId(clientId); - InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect, + InvokeOnTransportEvent(NetcodeEvent.Disconnect, clientId, default, m_RealTimeProvider.RealTimeSinceStartup); return true; } - case TransportNetworkEvent.Type.Data: + case TransportEvent.Data: { ReceiveMessages(clientId, pipeline, reader); return true; @@ -957,7 +892,7 @@ protected override void OnEarlyUpdate() Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " + "Use NetworkManager.OnTransportFailure to be notified of such events programmatically."); - InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, m_RealTimeProvider.RealTimeSinceStartup); + InvokeOnTransportEvent(NetcodeEvent.TransportFailure, 0, default, m_RealTimeProvider.RealTimeSinceStartup); return; } @@ -1180,13 +1115,13 @@ private void FlushSendQueuesForClientId(ulong clientId) /// public override void DisconnectLocalClient() { - if (m_State == State.Connected) + if (m_ServerClientId != default) { FlushSendQueuesForClientId(m_ServerClientId); if (m_Driver.Disconnect(ParseClientId(m_ServerClientId)) == 0) { - m_State = State.Disconnected; + m_ServerClientId = default; m_ReliableReceiveQueues.Remove(m_ServerClientId); ClearSendQueuesForClientId(m_ServerClientId); @@ -1194,7 +1129,7 @@ public override void DisconnectLocalClient() // If we successfully disconnect we dispatch a local disconnect message // this how uNET and other transports worked and so this is just keeping with the old behavior // should be also noted on the client this will call shutdown on the NetworkManager and the Transport - InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect, + InvokeOnTransportEvent(NetcodeEvent.Disconnect, m_ServerClientId, default, m_RealTimeProvider.RealTimeSinceStartup); @@ -1209,14 +1144,14 @@ public override void DisconnectLocalClient() public override void DisconnectRemoteClient(ulong clientId) { #if DEBUG - if (m_State != State.Listening) + if (!m_Driver.IsCreated) { Debug.LogWarning($"{nameof(DisconnectRemoteClient)} should only be called on a listening server!"); return; } #endif - if (m_State == State.Listening) + if (m_Driver.IsCreated) { FlushSendQueuesForClientId(clientId); @@ -1331,12 +1266,12 @@ public override void Initialize(NetworkManager networkManager = null) /// The incoming data payload /// The time the event was received, as reported by m_RealTimeProvider.RealTimeSinceStartup. /// Returns the event type - public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) + public override NetcodeEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) { clientId = default; payload = default; receiveTime = default; - return NetcodeNetworkEvent.Nothing; + return NetcodeEvent.Nothing; } /// @@ -1404,7 +1339,7 @@ public override void Send(ulong clientId, ArraySegment payload, NetworkDel DisconnectRemoteClient(clientId); // DisconnectRemoteClient doesn't notify SDK of disconnection. - InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect, + InvokeOnTransportEvent(NetcodeEvent.Disconnect, clientId, default(ArraySegment), m_RealTimeProvider.RealTimeSinceStartup); @@ -1520,10 +1455,9 @@ public override void Shutdown() DisposeInternals(); m_ReliableReceiveQueues.Clear(); - m_State = State.Disconnected; // We must reset this to zero because UTP actually re-uses clientIds if there is a clean disconnect - m_ServerClientId = 0; + m_ServerClientId = default; } private void ConfigureSimulator() @@ -1786,4 +1720,37 @@ public override int GetHashCode() } } } + + /// + /// Utility class to convert Unity Transport error codes to human-readable error messages. + /// + public static class ErrorUtilities + { + /// + /// Convert a Unity Transport error code to human-readable error message. + /// + /// Unity Transport error code. + /// ID of connection on which error occurred (unused). + /// Human-readable error message. + public static string ErrorToString(TransportError error, ulong connectionId) + { + return ErrorToFixedString((int)error).ToString(); + } + + internal static FixedString128Bytes ErrorToFixedString(int error) + { + switch ((TransportError)error) + { + case TransportError.NetworkVersionMismatch: + case TransportError.NetworkStateMismatch: + return "invalid connection state (likely stale/closed connection)"; + case TransportError.NetworkPacketOverflow: + return "packet is too large for the transport (likely need to increase MTU)"; + case TransportError.NetworkSendQueueFull: + return "send queue full (need to increase 'Max Send Queue Size' parameter)"; + default: + return FixedString.Format("unexpected error code {0}", error); + } + } + } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs index 87ffbdb418..bd9d9e937e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Transports/UnityTransportTests.cs @@ -189,7 +189,7 @@ public void UnityTransport_EmptySecurityStringsShouldThrow([Values("", null)] st networkManager.StartServer(); }); // Make sure StartServer failed - Assert.False(transport.NetworkDriver.IsCreated); + Assert.False(transport.GetNetworkDriver().IsCreated); Assert.False(networkManager.IsServer); Assert.False(networkManager.IsListening); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs index 2956f48fda..1f9a35390f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/PacketLossMetricsTests.cs @@ -64,9 +64,9 @@ public IEnumerator TrackPacketLossAsClient() var clientNetworkManager = m_ClientNetworkManagers[0]; var clientTransport = (UnityTransport)clientNetworkManager.NetworkConfig.NetworkTransport; - clientTransport.NetworkDriver.CurrentSettings.TryGet(out var parameters); + clientTransport.GetNetworkDriver().CurrentSettings.TryGet(out var parameters); parameters.PacketDropPercentage = m_PacketLossRate; - clientTransport.NetworkDriver.ModifySimulatorStageParameters(parameters); + clientTransport.GetNetworkDriver().ModifySimulatorStageParameters(parameters); var waitForPacketLossMetric = new WaitForGaugeMetricValues((clientNetworkManager.NetworkMetrics as NetworkMetrics).Dispatcher, NetworkMetricTypes.PacketLoss, diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs index ba68c4147f..03bd6c2c4c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs @@ -311,6 +311,8 @@ public IEnumerator DisconnectOnReliableSendQueueOverflow() yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events); + var serverClientId = m_Client1.ServerClientId; + m_Server.Shutdown(); var numSends = (maxSendQueueSize / 1024); @@ -322,7 +324,7 @@ public IEnumerator DisconnectOnReliableSendQueueOverflow() } LogAssert.Expect(LogType.Error, "Couldn't add payload of size 1024 to reliable send queue. " + - $"Closing connection {m_Client1.ServerClientId} as reliability guarantees can't be maintained."); + $"Closing connection {serverClientId} as reliability guarantees can't be maintained."); Assert.AreEqual(2, m_Client1Events.Count); Assert.AreEqual(NetworkEvent.Disconnect, m_Client1Events[1].Type); From 23eab5da941e38e145fc4f662852fcbaa6dacff7 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 9 Apr 2025 17:22:19 -0500 Subject: [PATCH 220/236] fix: In-Scene Placed Object Parenting, Serialization Order, and Transform Parent Detection (#3387) This resolves issues with in-scene placed NetworkObject parenting where the original parent has changed. This resolves the issue where loading a scene would not order the serialization of loaded in-scene placed NetworkObjects based on their parent-child hierarchy. This also resolves an issue where a `NetworkTransform` would think it was parented if nested under anything as well as removes the spamming error message when parenting a `NetworkObject` with `NetworkTransform` and using local space for synchronization (can be handled in documentation as to why this is a bad idea but there may be edge cases where a user might want to do this anyway). [MTT-11883](https://jira.unity3d.com/browse/MTT-11883) ## Changelog - Fixed: Issue where in-scene placed `NetworkObjects` could fail to synchronize its transform properly (especially without a `NetworkTransform`) if their parenting changes from the default when the scene is loaded and if the same scene remains loaded between network sessions while the parenting is completely different from the original hierarchy. - Changed: The scene loading event serialization order for in-scene placed `NetworkObject`s to be based on their parent-child hierarchy. - Changed: Removing the error message when a `NetworkObject` with `NetworkTransform` is parented and placed in local space when using rigidbody for motion. ## Testing and Documentation - Includes no additional tests (_requires manual testing due to scene loading constraints_). - No documentation changes or additions were necessary. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Components/NetworkTransform.cs | 12 +-- .../Runtime/Core/NetworkObject.cs | 93 +++++++++++-------- .../Runtime/SceneManagement/SceneEventData.cs | 26 +++++- .../Runtime/Spawning/NetworkSpawnManager.cs | 42 +++++---- 5 files changed, 107 insertions(+), 68 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 480152dc66..c4edcc652c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where in-scene placed `NetworkObjects` could fail to synchronize its transform properly (especially without a `NetworkTransform`) if their parenting changes from the default when the scene is loaded and if the same scene remains loaded between network sessions while the parenting is completely different from the original hierarchy. (#3387) - Fixed an issue in `UnityTransport` where the transport would accept sends on invalid connections, leading to a useless memory allocation and confusing error message. (#3382) - Fixed issue where the time delta that interpolators used would not be properly updated during multiple fixed update invocations within the same player loop frame. (#3355) - Fixed issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. (#3350) @@ -44,6 +45,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed the scene loading event serialization order for in-scene placed `NetworkObject`s to be based on their parent-child hierarchy. (#3387) - Changed the original `Lerp` interpolation type to `LegacyLerp`. (#3355) - Changed `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as being deprecated since this method is only used for internal testing purposes. (#3337) - Changed error thrown when attempting to build a dedicated server with Unity Transport that uses websockets to provide more useful information to the user. (#3336) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 92943ee0b5..10e95c6017 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -2074,13 +2074,6 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra // buffered values to the correct world or local space values. forceState = SwitchTransformSpaceWhenParented; } -#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - else if (InLocalSpace && m_UseRigidbodyForMotion) - { - // TODO: Provide more options than just FixedJoint - Debug.LogError($"[Rigidbody] WHen using a Rigidbody for motion, you cannot use {nameof(InLocalSpace)}! If parenting, use the integrated FixedJoint or use a Joint on Authority side."); - } -#endif // Check for parenting when synchronizing and/or teleporting if (isSynchronization || networkState.IsTeleportingNextFrame) @@ -3543,7 +3536,10 @@ private void InternalInitialization(bool isOwnershipChange = false) { if (CanCommitToTransform) { - InLocalSpace = transform.parent != null; + if (NetworkObject.HasParentNetworkObject(transform)) + { + InLocalSpace = true; + } } // Always apply this if SwitchTransformSpaceWhenParented is set. TickSyncChildren = true; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 51895a15e8..4b6c43feb4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -308,15 +308,18 @@ internal void OnValidate() /// private void CheckForInScenePlaced() { - if (PrefabUtility.IsPartOfAnyPrefab(this) && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0) + if (gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0) { - var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject); - var assetPath = AssetDatabase.GetAssetPath(prefab); - var sourceAsset = AssetDatabase.LoadAssetAtPath(assetPath); - if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash) + if (PrefabUtility.IsPartOfAnyPrefab(this)) { - InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash; - EditorUtility.SetDirty(this); + var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject); + var assetPath = AssetDatabase.GetAssetPath(prefab); + var sourceAsset = AssetDatabase.LoadAssetAtPath(assetPath); + if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash) + { + InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash; + EditorUtility.SetDirty(this); + } } IsSceneObject = true; @@ -335,6 +338,24 @@ private void CheckForInScenePlaced() } #endif // UNITY_EDITOR + internal bool HasParentNetworkObject(Transform transform) + { + if (transform.parent != null) + { + var networkObject = transform.parent.GetComponent(); + if (networkObject != null && networkObject != this) + { + return true; + } + + if (transform.parent.parent != null) + { + return HasParentNetworkObject(transform.parent); + } + } + return false; + } + /// /// Gets the NetworkManager that owns this NetworkObject instance /// @@ -2295,7 +2316,7 @@ private void OnTransformParentChanged() // we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one. internal static HashSet OrphanChildren = new HashSet(); - internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false) + internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false, bool enableNotification = true) { if (!AutoObjectParentSync) { @@ -2368,7 +2389,10 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // to WorldPositionStays which can cause scaling issues if the parent's // scale is not the default (Vetctor3.one) value. transform.SetParent(null, m_CachedWorldPositionStays); - InvokeBehaviourOnNetworkObjectParentChanged(null); + if (enableNotification) + { + InvokeBehaviourOnNetworkObjectParentChanged(null); + } return true; } @@ -2393,7 +2417,10 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa } SetCachedParent(parentObject.transform); transform.SetParent(parentObject.transform, m_CachedWorldPositionStays); - InvokeBehaviourOnNetworkObjectParentChanged(parentObject); + if (enableNotification) + { + InvokeBehaviourOnNetworkObjectParentChanged(parentObject); + } return true; } @@ -3030,6 +3057,8 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager { var obj = new SceneObject { + HasParent = transform.parent != null, + WorldPositionStays = m_CachedWorldPositionStays, NetworkObjectId = NetworkObjectId, OwnerClientId = OwnerClientId, IsPlayerObject = IsPlayerObject, @@ -3046,31 +3075,16 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager TargetClientId = targetClientId }; - NetworkObject parentNetworkObject = null; - - if (!AlwaysReplicateAsRoot && transform.parent != null) + // Handle Parenting + if (!AlwaysReplicateAsRoot && obj.HasParent) { - parentNetworkObject = transform.parent.GetComponent(); - // In-scene placed NetworkObjects parented under GameObjects with no NetworkObject - // should set the has parent flag and preserve the world position stays value - if (parentNetworkObject == null && obj.IsSceneObject) - { - obj.HasParent = true; - obj.WorldPositionStays = m_CachedWorldPositionStays; - } - } + var parentNetworkObject = transform.parent.GetComponent(); - if (parentNetworkObject != null) - { - obj.HasParent = true; - obj.ParentObjectId = parentNetworkObject.NetworkObjectId; - obj.WorldPositionStays = m_CachedWorldPositionStays; - var latestParent = GetNetworkParenting(); - var isLatestParentSet = latestParent != null && latestParent.HasValue; - obj.IsLatestParentSet = isLatestParentSet; - if (isLatestParentSet) + if (parentNetworkObject) { - obj.LatestParent = latestParent.Value; + obj.ParentObjectId = parentNetworkObject.NetworkObjectId; + obj.LatestParent = GetNetworkParenting(); + obj.IsLatestParentSet = obj.LatestParent != null && obj.LatestParent.HasValue; } } @@ -3083,12 +3097,6 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays; var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays; - // Always synchronize in-scene placed object's scale using local space - if (obj.IsSceneObject) - { - syncScaleLocalSpaceRelative = obj.HasParent; - } - // If auto object synchronization is turned off if (!AutoObjectParentSync) { @@ -3166,6 +3174,15 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf var bufferSerializer = new BufferSerializer(new BufferSerializerReader(reader)); networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId); + // If we are an in-scene placed NetworkObject and we originally had a parent but when synchronized we are + // being told we do not have a parent, then we want to clear the latest parent so it is not automatically + // "re-parented" to the original parent. This can happen if not unloading the scene and the parenting of + // the in-scene placed Networkobject changes several times over different sessions. + if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.m_LatestParent.HasValue) + { + networkObject.m_LatestParent = null; + } + // Spawn the NetworkObject networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene); diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 7f03e08493..943f54aff2 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -329,7 +329,14 @@ internal void AddSpawnedNetworkObjects() m_NetworkObjectsSync.Add(sobj); } } + SortObjectsToSync(); + } + /// + /// Used to order the object serialization for both synchronization and scene loading + /// + private void SortObjectsToSync() + { // Sort by INetworkPrefabInstanceHandler implementation before the // NetworkObjects spawned by the implementation m_NetworkObjectsSync.Sort(SortNetworkObjects); @@ -671,20 +678,31 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) // If distributed authority mode and sending to the service, then ignore observers var distributedAuthoritySendingToService = distributedAuthority && TargetClientId == NetworkManager.ServerClientId; + // Clear our objects to sync and build a list of the in-scene placed NetworkObjects instantiated and spawned locally + m_NetworkObjectsSync.Clear(); foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects) { foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value) { if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService) { - // Serialize the NetworkObject - var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority); - sceneObject.Serialize(writer); - numberOfObjects++; + m_NetworkObjectsSync.Add(keyValuePairBySceneHandle.Value); } } } + // Sort the objects to sync based on parenting hierarchy + SortObjectsToSync(); + + // Serialize the sorted objects to sync. + foreach (var objectToSycn in m_NetworkObjectsSync) + { + // Serialize the NetworkObject + var sceneObject = objectToSycn.GetMessageSceneObject(TargetClientId, distributedAuthority); + sceneObject.Serialize(writer); + numberOfObjects++; + } + // Write the number of despawned in-scene placed NetworkObjects writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count); // Write the scene handle and GlobalObjectIdHash value diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 0e9558f4bc..e87da1f489 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -884,7 +884,6 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default; var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default; var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays; - var isSpawnedByPrefabHandler = false; // If scene management is disabled or the NetworkObject was dynamically spawned if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject) @@ -917,33 +916,41 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO networkObject.DontDestroyWithOwner = sceneObject.DontDestroyWithOwner; networkObject.Ownership = (NetworkObject.OwnershipStatus)sceneObject.OwnershipFlags; - var nonNetworkObjectParent = false; // SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject) // This is a special case scenario where a late joining client has joined and loaded one or // more scenes that contain nested in-scene placed NetworkObject children yet the server's - // synchronization information does not indicate the NetworkObject in question has a parent. - // Under this scenario, we want to remove the parent before spawning and setting the transform values. + // synchronization information does not indicate the NetworkObject in question has a parent =or= + // the parent has changed. + // For this we will want to remove the parent before spawning and setting the transform values based + // on several possible scenarios. if (sceneObject.IsSceneObject && networkObject.transform.parent != null) { var parentNetworkObject = networkObject.transform.parent.GetComponent(); - // if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not - // include parenting, then we need to force the removal of that parent - if (!sceneObject.HasParent && parentNetworkObject) - { - // remove the parent - networkObject.ApplyNetworkParenting(true, true); - } - else if (sceneObject.HasParent && !parentNetworkObject) + + // special case to handle being parented under a GameObject with no NetworkObject + nonNetworkObjectParent = !parentNetworkObject && sceneObject.HasParent; + + // If the in-scene placed NetworkObject has a parent NetworkObject... + if (parentNetworkObject) { - nonNetworkObjectParent = true; + // Then remove the parent only if: + // - The authority says we don't have a parent (but locally we do). + // - The auhtority says we have a parent but either of the two are true: + // -- It isn't the same parent. + // -- It was parented using world position stays. + if (!sceneObject.HasParent || (sceneObject.IsLatestParentSet + && (sceneObject.LatestParent.Value != parentNetworkObject.NetworkObjectId || sceneObject.WorldPositionStays))) + { + // If parenting without notifications then we are temporarily removing the parent to set the transform + // values before reparenting under the current parent. + networkObject.ApplyNetworkParenting(true, true, enableNotification: !sceneObject.HasParent); + } } } - // Set the transform unless we were spawned by a prefab handler - // Note: prefab handlers are provided the position and rotation - // but it is up to the user to set those values - if (sceneObject.HasTransform && !isSpawnedByPrefabHandler) + // Set the transform only if the sceneObject includes transform information. + if (sceneObject.HasTransform) { // If world position stays is true or we have auto object parent synchronization disabled // then we want to apply the position and rotation values world space relative @@ -986,7 +993,6 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO networkObject.SetNetworkParenting(parentId, worldPositionStays); } - // Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL // until the scene is loaded. They are then migrated back into the newly loaded and currently active scene. if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) From 9329b80c1e017d660e84b7e3a690d68f47db3434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 11 Apr 2025 14:54:29 +0200 Subject: [PATCH 221/236] ci: webgl build job timeout extended (#3389) It was noted that webgl builds started timing out more often in the span of the last month so this PR aims to increase the timeout for this job --- .yamato/webgl-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yamato/webgl-build.yml b/.yamato/webgl-build.yml index 7b9756a3bf..3729ffb965 100644 --- a/.yamato/webgl-build.yml +++ b/.yamato/webgl-build.yml @@ -45,7 +45,7 @@ webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: # Engine is initialized in ‘nographics’ mode since we don't need any graphics for this case (--extra-editor-arg=-nographics) # In case of failure the job will be rerunned once (--reruncount=1) with clean library (--clean-library-on-rerun) # This will perform only building phase (--build-only) with a timeout of 3m (--timeout=1800) - - UnifiedTestRunner --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts_path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=1800 + - UnifiedTestRunner --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts_path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=2400 artifacts: logs: paths: From f3ec30700246df50b96d2025954d5301185743a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 11 Apr 2025 23:37:31 +0200 Subject: [PATCH 222/236] ci: backport-verification test (#3385) Due to nature of NGO of having 2 development branches (`develop` for NGOv1.X and `develop-2.0.0` for NGOv2.X) we need to ensure that relevant changes are landing in only one branch (when it's relevant to only develop-2.0.0 for example) or both of them. For people not familiar with NGO structure (but also for us) it might be easy to omit the fact of making a backport (or forward port) so this PR includes a GitHub workflow that will check if "**## Backport**" section exist in the PR thus making it mandatory. This will add a bit of work to each PR but it will ensure that developers get reminded about backports and hopefully this will lead to - Easier PR tracking since every PR will contain a description of a backport or reasoning why a backport is not needed - Reduction of potential forgotten backports which in turn could increase overall quality of NGO I tested this workflow on my fork of NGO repo and seems to be working as expected ## Backport Backport created in https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3393. After this PR gets approved I will modify branch protection rules to block PRs from merging if this check fails. --------- Co-authored-by: Emma --- .github/pull_request_template.md | 12 ++++++-- .github/workflows/backport-verification.yml | 32 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/backport-verification.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index efe4270525..bdf3f3801f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,8 +2,6 @@ - - ## Changelog - Added: The package whose Changelog should be added to should be in the header. Delete the changelog section entirely if it's not needed. @@ -26,3 +24,13 @@ - [ ] Deprecation of the API is explained in the CHANGELOG. - [ ] The users can understand why this API was removed and what they should use instead. --> + +## Backport + + \ No newline at end of file diff --git a/.github/workflows/backport-verification.yml b/.github/workflows/backport-verification.yml new file mode 100644 index 0000000000..82176f64bc --- /dev/null +++ b/.github/workflows/backport-verification.yml @@ -0,0 +1,32 @@ +# This workflow is designed to verify that the pull request description contains a "## Backport" section, which is important as a reminder to account for backports for anyone that works with NGO repository. +# We have 2 development branches (develop and develop-2.0.0) and we need to ensure that relevant changes are landing in only one or both of them +# If the "##Backport" section is missing, the workflow will fail and block the PR from merging, prompting the developer to add this section. + +# The workflow is configured to run when PR is created as well as when it is edited which also counts simple description edits. + +name: "NGO - Backport Verification" + +on: + pull_request: + types: [opened, edited] + branches: + - develop + - develop-2.0.0 + +jobs: + backport-verification: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Check PR description + uses: actions/github-script@v6 + with: + script: | + const pr = context.payload.pull_request; + const body = pr.body || ''; + + if (!body.includes('## Backport')) { + core.setFailed('PR description must include a "## Backport" section. Please add this section and provide information about this PR backport to develop or develop-2.0.0 branch respectively or explain why backport is not needed.'); + } \ No newline at end of file From dfadc33501f5be8c7a8b9b0bc2e4e57c7733d850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 11 Apr 2025 23:44:36 +0200 Subject: [PATCH 223/236] chore: Updated support template to redirect users to Discord or Discussions (#3391) This PR aims to modify the template when user wants to create support related issue. The goal is to redirect those to either DIscord or Unity Discussions where user would get much more (and faster) support and by doing this we may minimize the amount of GitHub Issues we need to take care of ## Backport PR is also backported to develop branch in https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3392 --- .github/ISSUE_TEMPLATE/support.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/support.md b/.github/ISSUE_TEMPLATE/support.md index 0033a651de..2383875723 100644 --- a/.github/ISSUE_TEMPLATE/support.md +++ b/.github/ISSUE_TEMPLATE/support.md @@ -7,6 +7,7 @@ assignees: '' --- -Post your questions or problems here. +For support related questions we HIGHLY recommend to post a message either on the [Unity Discussions](https://discussions.unity.com/tag/netcode-for-gameobjects) or on our [Discord Community](https://discord.gg/TqNeJTtC) where you can get help from the community and the developers. +Those forums will get you the fastest response and are the best place to ask for help. -For general questions, networking advice or discussions about the Netcode for GameObjects, you can also reach us on our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/). +If you still feel like you want to open a support issue as an GitHub Issue, please make sure to include as much information as possible (also including any relevant code/project) to help us understand your problem. \ No newline at end of file From b041d76e9a88d54778a0c15043c599e554fe6e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Sat, 12 Apr 2025 00:01:58 +0200 Subject: [PATCH 224/236] chore: [Backport] GitHub actions mirrored from develop (#3394) This PR ports all new GitHub Actions that were added to develop branch. This will not have any temporary effect since GitHub Actions utilizes configuration from main branch (currently develop) but this ensures that when we will change main branch to be develop-2.0.0 everything will be working in the same way ## Backport This PR ports changes that were included in https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3194 and https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3380 --- .github/workflows/assignee-management.yaml | 70 +++++++++++++++++++ .github/workflows/backport-verification.yml | 2 +- .github/workflows/conventional-pr.yml | 1 - .github/workflows/conversation-labels.yaml | 51 ++++++++++++++ .github/workflows/mark-stale-issue.yaml | 39 +++++++++++ .../remove-labels-on-issue-close.yaml | 40 +++++++++++ 6 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/assignee-management.yaml create mode 100644 .github/workflows/conversation-labels.yaml create mode 100644 .github/workflows/mark-stale-issue.yaml create mode 100644 .github/workflows/remove-labels-on-issue-close.yaml diff --git a/.github/workflows/assignee-management.yaml b/.github/workflows/assignee-management.yaml new file mode 100644 index 0000000000..b330a01191 --- /dev/null +++ b/.github/workflows/assignee-management.yaml @@ -0,0 +1,70 @@ +# This workflow manages issue assignments and related label changes: +# 1. When someone is assigned to an issue: +# - Removes "stat:awaiting-triage" label +# - Adds "stat:Investigating" label +# 2. When all assignees are removed from an issue: +# - Adds "stat:awaiting-triage" label +# - Removes "stat:Investigating" label +# 3. When "stat:Investigating" label is added: +# - Automatically assigns the issue to the person who added the label + +name: Handle Issue Assignment and Labels + +on: + issues: + types: [assigned, unassigned, labeled] + +env: + AWAITING-TRIAGE_LABEL: stat:awaiting-triage + INVESTIGATING_LABEL: stat:Investigating + +jobs: + handle_assignment: + name: Handle Issue Assignment Changes + if: ${{ !github.event.issue.pull_request && github.event.issue.state == 'open' }} + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - name: Handle Assignment Changes + run: | + if [[ "${{ github.event.action }}" == "assigned" ]]; then + # Someone was assigned - remove awaiting-triage, add investigating + echo "ADD=${{ env.INVESTIGATING_LABEL }}" >> $GITHUB_ENV + echo "REMOVE=${{ env.AWAITING-TRIAGE_LABEL }}" >> $GITHUB_ENV + elif [[ "${{ github.event.action }}" == "unassigned" ]]; then + # Check if there are any remaining assignees + ASSIGNEES=$(echo '${{ toJson(github.event.issue.assignees) }}' | jq length) + if [[ "$ASSIGNEES" == "0" ]]; then + # No assignees left - add awaiting-triage, remove investigating + echo "ADD=${{ env.AWAITING-TRIAGE_LABEL }}" >> $GITHUB_ENV + echo "REMOVE=${{ env.INVESTIGATING_LABEL }}" >> $GITHUB_ENV + fi + fi + + - name: Update Labels + if: env.ADD != '' || env.REMOVE != '' + run: gh issue edit "$NUMBER" --add-label "$ADD" --remove-label "$REMOVE" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + + handle_investigating_label: + name: Handle Investigating Label Addition + if: ${{ github.event.action == 'labeled' && github.event.label.name == 'stat:Investigating' && !github.event.issue.pull_request && github.event.issue.state == 'open' }} + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - name: Assign Issue to person that added Investigating Label + run: | + # Assign the issue to the person who added the label + gh issue edit "$NUMBER" --add-assignee "$ACTOR" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + ACTOR: ${{ github.actor }} diff --git a/.github/workflows/backport-verification.yml b/.github/workflows/backport-verification.yml index 82176f64bc..aaeb1aa739 100644 --- a/.github/workflows/backport-verification.yml +++ b/.github/workflows/backport-verification.yml @@ -5,7 +5,7 @@ # The workflow is configured to run when PR is created as well as when it is edited which also counts simple description edits. name: "NGO - Backport Verification" - + on: pull_request: types: [opened, edited] diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 9b86504074..8e783eec55 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -6,7 +6,6 @@ on: branches: - develop - develop-2.0.0 - # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" diff --git a/.github/workflows/conversation-labels.yaml b/.github/workflows/conversation-labels.yaml new file mode 100644 index 0000000000..0297464bc5 --- /dev/null +++ b/.github/workflows/conversation-labels.yaml @@ -0,0 +1,51 @@ +# This workflow will update issues with proper "conversation related" labels. This mean that stat:awaiting-repsonse label will be present after Unity account made comments and stat:reply-needed will be present when user made latest comment + +name: Update conversation labels of the issue + +# Trigger for the workflow +# This trigger will populate the github.event object +# Details on properties are here: https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=created#issue_comment +on: + issue_comment: + types: [created] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: true + +# Define labels here +env: + AWAITING_RESPONSE: stat:awaiting-response + REPLY_NEEDED: stat:reply-needed + +jobs: + conversation_labels: + name: Calculate and update conversation labels of the issue + if: ${{ !github.event.issue.pull_request && github.event.issue.state == 'open' }} + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - name: Calculate labels + run: | + + if [[ "${{ github.event.comment.author_association }}" == "MEMBER" ]]; then + # Unity member commented - add awaiting-response, remove reply-needed + echo "ADD=${{ env.AWAITING_RESPONSE }}" >> $GITHUB_ENV + echo "REMOVE=${{ env.REPLY_NEEDED }}" >> $GITHUB_ENV + else + # Non-Unity member commented - add reply-needed, remove awaiting-response + echo "ADD=${{ env.REPLY_NEEDED }}" >> $GITHUB_ENV + echo "REMOVE=${{ env.AWAITING_RESPONSE }}" >> $GITHUB_ENV + fi + + - name: Update labels + # This runs a command using the github cli: https://cli.github.com/manual/gh_issue_edit + # If $ADD is set, it will add the label, otherwise it will remove the label + # There is no need to check if $ADD or $REMOVE is set, as the command will ignore it if it is empty + run: gh issue edit "$NUMBER" --add-label "$ADD" --remove-label "$REMOVE" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} diff --git a/.github/workflows/mark-stale-issue.yaml b/.github/workflows/mark-stale-issue.yaml new file mode 100644 index 0000000000..2e77192183 --- /dev/null +++ b/.github/workflows/mark-stale-issue.yaml @@ -0,0 +1,39 @@ +# This workflow utilises an existing implementation (https://github.com/actions/stale) that performs the following actions: +# 1) Adds "stale" label to issues that have "stat:awaiting-response" for more than 30 days meaning that since we don't have enough information we may potentially close such issue +# 2) Closes issues that have been marked as "stale" for more than 30 days + +# This affects only Issues but at some point we may also consider rules for PRs + +name: Mark or Close Stale Issues + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' # Runs daily at midnight + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - uses: actions/stale@v9 + with: + # Only mark issues (not PRs) as stale + any-of-labels: 'stat:awaiting-response' + days-before-stale: 30 + days-before-close: 30 + stale-issue-label: 'Stale' + exempt-issue-labels: 'stat:import,stat:imported' + stale-issue-message: > + This issue has been automatically marked as stale because it has been awaiting + response for over 30 days without any activity. + + Please update the issue with any new information or it may be closed in 30 days. + close-issue-message: > + This issue has been automatically closed because it has been stale for 30 days + without any activity. Feel free to reopen if you have new information to add. + # Prevent the action from marking/closing PRs + days-before-pr-stale: -1 + days-before-pr-close: -1 diff --git a/.github/workflows/remove-labels-on-issue-close.yaml b/.github/workflows/remove-labels-on-issue-close.yaml new file mode 100644 index 0000000000..2ac5145e16 --- /dev/null +++ b/.github/workflows/remove-labels-on-issue-close.yaml @@ -0,0 +1,40 @@ +# This workflow will remove almost all labels from closed issues beside ones that could be relevant for future tracking like: "port:", "type:", "priority:", "regression" and "stat:imported" + +name: Remove labels when issue is closed + +on: + issues: + types: [closed] # We want it to run on closed issues + +jobs: + remove_labels: + name: Calculate and remove issue labels + if: ${{ !github.event.issue.pull_request }} # This is needed to distinguish from PRs (which we don't want to affect) + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - name: Find labels to remove + id: data + run: | + # Convert labels to array and filter out type: labels + LABELS_TO_REMOVE=($(echo "$EXISTING_LABELS" | jq -r '.[] | select(startswith("type:") or startswith("port:") or startswith("priority:") or . == "regression" or . == "stat:imported" | not)')) + + # Only proceed if we have labels to remove + if [ ${#LABELS_TO_REMOVE[@]} -gt 0 ]; then + echo "REMOVE_LABELS=$(IFS=,; echo "${LABELS_TO_REMOVE[*]}")" >> $GITHUB_ENV + echo "HAS_LABELS=true" >> $GITHUB_ENV + else + echo "HAS_LABELS=false" >> $GITHUB_ENV + fi + env: + EXISTING_LABELS: ${{ toJson(github.event.issue.labels.*.name) }} + + - name: Remove labels + if: ${{ env.REMOVE_LABELS != '' }} + run: gh issue edit "$NUMBER" --remove-label "$REMOVE_LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} From ece63de275c51425abbb4a93bfaa4eb42be5faa1 Mon Sep 17 00:00:00 2001 From: michalChrobot Date: Sat, 12 Apr 2025 00:08:27 +0200 Subject: [PATCH 225/236] Updated backport-verification test to rerun on commits --- .github/workflows/backport-verification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport-verification.yml b/.github/workflows/backport-verification.yml index aaeb1aa739..0ebe899391 100644 --- a/.github/workflows/backport-verification.yml +++ b/.github/workflows/backport-verification.yml @@ -8,7 +8,7 @@ name: "NGO - Backport Verification" on: pull_request: - types: [opened, edited] + types: [opened, edited, synchronize, reopened] branches: - develop - develop-2.0.0 From fe532aa7a3b325f0c83bd5ee77e926e8c1a35a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Sat, 12 Apr 2025 01:14:15 +0200 Subject: [PATCH 226/236] docs: [2.X] fixes of PVP exceptions (round 2) (#3375) This PR is a continuation of https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3225 and focuses on fixing PVP errors related to PVP-151-1 "**_Public APIs should be documented_**" and all remaining PVP errors **present in pvpExceptions.json file** We currently have 137 errors to address so this PR will be a collaborative effort. If anyone want's to add to it what we should do is: 1. Check errors present in pvpExceptions.json file (preferably under "PVP-150-1", choose some to fix 2. Address those errors and when making commit remove the fixed errors from pvpExceptions file In general [new CI implementation](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3193) will catch all new errors like this so all we need to do is fix the present ones. This PR **does not** focus on PVP present in gold profile but if capacity allows, those can also be addressed. **Error present in "PVP-41-1" is to be expected** so there is no need of fixing this one! Another thing is that we could ignore error related to missing APIs for tests (so TestHelpers, EditorTests, RuntimeTests) because those are not affecting users in any way. ## Backport This PR has its equivalent in the sense of https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3374 --- .../Components/AnticipatedNetworkTransform.cs | 7 + .../Runtime/Configuration/NetworkConfig.cs | 14 +- .../Runtime/Configuration/NetworkPrefab.cs | 26 ++++ .../Runtime/Configuration/NetworkPrefabs.cs | 11 ++ .../Runtime/Connection/NetworkClient.cs | 6 + .../Connection/NetworkConnectionManager.cs | 3 + .../Runtime/Core/NetworkBehaviour.cs | 18 +++ .../Runtime/Core/NetworkManager.cs | 11 ++ .../Runtime/Core/NetworkObject.cs | 99 ++++++++++++- .../Runtime/Messaging/CustomMessageManager.cs | 2 + ...rializationForGenericParameterAttribute.cs | 4 + .../GenerateSerializationForTypeAttribute.cs | 4 + .../Runtime/Messaging/RpcAttributes.cs | 45 +++++- .../Runtime/Messaging/RpcParams.cs | 57 +++++++- .../AnticipatedNetworkVariable.cs | 52 ++++++- .../Collections/NetworkList.cs | 3 + .../NetworkVariable/NetworkVariable.cs | 21 +++ .../NetworkVariable/NetworkVariableBase.cs | 18 ++- .../NetworkVariableSerialization.cs | 5 +- .../UserNetworkVariableSerialization.cs | 2 + .../Runtime/Spawning/NetworkSpawnManager.cs | 3 + .../Runtime/Transports/NetworkTransport.cs | 6 +- pvpExceptions.json | 137 ------------------ 23 files changed, 403 insertions(+), 151 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index 913447beb1..193a292136 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -286,6 +286,7 @@ private void ProcessSmoothing() // TODO: This does not handle OnFixedUpdate // This requires a complete overhaul in this class to switch between using // NetworkRigidbody's position and rotation values. + /// public override void OnUpdate() { ProcessSmoothing(); @@ -422,6 +423,7 @@ protected internal override void InternalOnNetworkPostSpawn() } } + /// public override void OnNetworkSpawn() { if (NetworkManager.DistributedAuthorityMode) @@ -445,6 +447,7 @@ public override void OnNetworkSpawn() NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject); } + /// public override void OnNetworkDespawn() { if (m_AnticipatedObject != null) @@ -459,6 +462,7 @@ public override void OnNetworkDespawn() base.OnNetworkDespawn(); } + /// public override void OnDestroy() { if (m_AnticipatedObject != null) @@ -510,6 +514,7 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds m_CurrentSmoothTime = 0; } + /// protected override void OnBeforeUpdateTransformState() { // this is called when new data comes from the server @@ -517,12 +522,14 @@ protected override void OnBeforeUpdateTransformState() m_OutstandingAuthorityChange = true; } + /// protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState) { base.OnNetworkTransformStateUpdated(ref oldState, ref newState); ApplyAuthoritativeState(); } + /// protected override void OnTransformUpdated() { if (CanCommitToTransform || m_AnticipatedObject == null) diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs index 826fb15ecf..00e7719e4a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConfig.cs @@ -39,6 +39,9 @@ public class NetworkConfig [Tooltip("When set, NetworkManager will automatically create and spawn the assigned player prefab. This can be overridden by adding it to the NetworkPrefabs list and selecting override.")] public GameObject PlayerPrefab; + /// + /// The collection of network prefabs that can be spawned across the network + /// [SerializeField] public NetworkPrefabs Prefabs = new NetworkPrefabs(); @@ -159,12 +162,21 @@ public class NetworkConfig /// public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets) + /// + /// Determines whether to use the client-server or distributed authority network topology + /// [Tooltip("Determines whether to use the client-server or distributed authority network topology.")] public NetworkTopologyTypes NetworkTopology; + /// + /// Internal flag for Cloud Multiplayer Build service integration + /// [HideInInspector] public bool UseCMBService; + /// + /// When enabled (default), the player prefab will automatically be spawned client-side upon the client being approved and synchronized + /// [Tooltip("When enabled (default), the player prefab will automatically be spawned (client-side) upon the client being approved and synchronized.")] public bool AutoSpawnPlayerPrefabClientSide = true; @@ -205,7 +217,7 @@ internal NetworkConfig Copy() return networkConfig; } -#endif +#endif #if MULTIPLAYER_TOOLS diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs index 9cc2158cc1..c6f30d2835 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs @@ -57,6 +57,11 @@ public class NetworkPrefab ///
public GameObject OverridingTargetPrefab; + /// + /// Compares this NetworkPrefab with another to determine equality + /// + /// The NetworkPrefab to compare against + /// True if all fields match between the two NetworkPrefabs, false otherwise public bool Equals(NetworkPrefab other) { return Override == other.Override && @@ -66,6 +71,12 @@ public bool Equals(NetworkPrefab other) OverridingTargetPrefab == other.OverridingTargetPrefab; } + /// + /// Gets the GlobalObjectIdHash of the source prefab based on the current override settings + /// + /// The hash value identifying the source prefab + /// Thrown when required prefab references are missing or invalid + /// Thrown when Override has an invalid value public uint SourcePrefabGlobalObjectIdHash { get @@ -98,6 +109,12 @@ public uint SourcePrefabGlobalObjectIdHash } } + /// + /// Gets the GlobalObjectIdHash of the target prefab when using prefab overrides + /// + /// The hash value identifying the target prefab, or 0 if no override is set + /// Thrown when required prefab references are missing or invalid + /// Thrown when Override has an invalid value public uint TargetPrefabGlobalObjectIdHash { get @@ -122,6 +139,11 @@ public uint TargetPrefabGlobalObjectIdHash } } + /// + /// Validates the NetworkPrefab configuration to ensure all required fields are properly set + /// + /// Optional index used for error reporting when validating lists of prefabs + /// True if the NetworkPrefab is valid and ready for use, false otherwise public bool Validate(int index = -1) { NetworkObject networkObject; @@ -224,6 +246,10 @@ public bool Validate(int index = -1) return true; } + /// + /// Returns a string representation of this NetworkPrefab's source and target hash values + /// + /// A string containing the source and target hash values public override string ToString() { return $"{{SourceHash: {SourceHashToOverride}, TargetHash: {TargetPrefabGlobalObjectIdHash}}}"; diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index 42758f7e78..6e801f5b61 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -39,6 +39,9 @@ public class NetworkPrefabs [NonSerialized] public Dictionary OverrideToNetworkPrefab = new Dictionary(); + /// + /// Gets the read-only list of all registered network prefabs + /// public IReadOnlyList Prefabs => m_Prefabs; [NonSerialized] @@ -62,6 +65,9 @@ private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) m_Prefabs.Remove(networkPrefab); } + /// + /// Finalizer that ensures proper cleanup of network prefab resources + /// ~NetworkPrefabs() { Shutdown(); @@ -84,6 +90,7 @@ internal void Shutdown() /// Processes the if one is present for use during runtime execution, /// else processes . ///
+ /// When true, logs warnings about invalid prefabs that are removed during initialization public void Initialize(bool warnInvalid = true) { m_Prefabs.Clear(); @@ -156,6 +163,8 @@ public void Initialize(bool warnInvalid = true) /// /// Add a new NetworkPrefab instance to the list /// + /// The NetworkPrefab to add + /// True if the prefab was successfully added, false if it was invalid or already registered /// /// The framework does not synchronize this list between clients. Any runtime changes must be handled manually. /// @@ -177,6 +186,7 @@ public bool Add(NetworkPrefab networkPrefab) /// /// Remove a NetworkPrefab instance from the list /// + /// The NetworkPrefab to remove /// /// The framework does not synchronize this list between clients. Any runtime changes must be handled manually. /// @@ -199,6 +209,7 @@ public void Remove(NetworkPrefab prefab) /// /// Remove a NetworkPrefab instance with matching from the list /// + /// The GameObject to match against for removal /// /// The framework does not synchronize this list between clients. Any runtime changes must be handled manually. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs index ff6ed614e1..eefe269ca4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkClient.cs @@ -34,8 +34,14 @@ public class NetworkClient ///
internal bool IsApproved { get; set; } + /// + /// Defines the network topology type being used for the current network session + /// public NetworkTopologyTypes NetworkTopologyType { get; internal set; } + /// + /// Indicates whether this client is running in Distributed Authority Host mode + /// public bool DAHost { get; internal set; } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index a95df88a4e..dbdff3ca2b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -60,6 +60,9 @@ public enum ConnectionEvent /// public struct ConnectionEventData { + /// + /// The type of connection event that occurred + /// public ConnectionEvent EventType; /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 1f98d0b840..7113917944 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -6,8 +6,15 @@ namespace Unity.Netcode { + /// + /// Exception thrown when an RPC (Remote Procedure Call) encounters an error during execution + /// public class RpcException : Exception { + /// + /// Initializes a new instance of the RpcException class with a specified error message + /// + /// The message that describes the error public RpcException(string message) : base(message) { @@ -694,6 +701,9 @@ public virtual void OnNetworkSpawn() { } /// protected virtual void OnNetworkPostSpawn() { } + /// + /// Internal implementation of post-spawn functionality. Called after OnNetworkSpawn to handle internal post-spawn operations. + /// protected internal virtual void InternalOnNetworkPostSpawn() { } /// @@ -708,6 +718,9 @@ protected internal virtual void InternalOnNetworkPostSpawn() { } /// protected virtual void OnNetworkSessionSynchronized() { } + /// + /// Internal implementation of network session synchronization. Handles the internal processing of session synchronization events. + /// protected internal virtual void InternalOnNetworkSessionSynchronized() { } /// @@ -1386,6 +1399,11 @@ protected virtual void OnSynchronize(ref BufferSerializer serializer) wher } + /// + /// Called when network conditions require reanticipation of game state. + /// Override this method to handle adjustments needed when network latency changes. + /// + /// The most recent round trip time measurement in seconds public virtual void OnReanticipate(double lastRoundTripTime) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index eed0bc497c..f0e388d72e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -201,6 +201,9 @@ internal void HandleRedistributionToClients() internal List DeferredDespawnObjects = new List(); + /// + /// Gets the client identifier of the current session owner in distributed authority mode + /// public ulong CurrentSessionOwner { get; internal set; } /// @@ -312,6 +315,10 @@ private void UpdateTopology() } } + /// + /// Processes network-related updates for a specific update stage in the frame + /// + /// The current network update stage being processed public void NetworkUpdate(NetworkUpdateStage updateStage) { switch (updateStage) @@ -643,6 +650,10 @@ public event Action OnTransportFailure remove => ConnectionManager.OnTransportFailure -= value; } + /// + /// Delegate for handling network state reanticipation events + /// + /// The most recent round-trip time measurement in seconds between client and server public delegate void ReanticipateDelegate(double lastRoundTripTime); /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 4b6c43feb4..502ffefcb6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -533,22 +533,27 @@ public enum OwnershipStatus /// When set, this instance will have no permissions (i.e. cannot distribute, transfer, etc). /// None = 0, + /// /// When set, this instance will be automatically redistributed when a client joins (if not locked or no request is pending) or leaves. /// Distributable = 1 << 0, + /// /// When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked). /// Transferable = 1 << 1, + /// /// When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred). /// RequestRequired = 1 << 2, + /// /// When set, only the current session owner may have ownership over this object. /// SessionOwner = 1 << 3, + /// /// Used within the inspector view only. When selected it will set the Distributable, Transferable, and RequestRequired flags or if those flags are already set it will select the SessionOwner flag by itself. /// @@ -648,10 +653,29 @@ public bool SetOwnershipLock(bool lockOwnership = true) /// public enum OwnershipPermissionsFailureStatus { + /// + /// The NetworkObject is locked and ownership cannot be acquired + /// Locked, + + /// + /// The NetworkObject requires an ownership request via RequestOwnership + /// RequestRequired, + + /// + /// The NetworkObject is already processing an ownership request and ownership cannot be acquired at this time + /// RequestInProgress, + + /// + /// The NetworkObject does not have the OwnershipStatus.Transferable flag set and ownership cannot be acquired + /// NotTransferrable, + + /// + /// The NetworkObject has the OwnershipStatus.SessionOwner flag set and ownership cannot be acquired + /// SessionOwnerOnly } @@ -679,11 +703,35 @@ public enum OwnershipPermissionsFailureStatus /// public enum OwnershipRequestStatus { + /// + /// The request for ownership was sent (does not mean it will be granted, but the request was sent) + /// RequestSent, + + /// + /// The current client is already the owner (no need to request ownership) + /// AlreadyOwner, + + /// + /// The OwnershipStatus.RequestRequired flag is not set on this NetworkObject + /// RequestRequiredNotSet, + + /// + /// The current owner has locked ownership which means requests are not available at this time + /// Locked, + + /// + /// There is already a known request in progress. You can scan for ownership changes and try again + /// after a specific period of time or no longer attempt to request ownership + /// RequestInProgress, + + /// + /// This object is marked as SessionOwnerOnly and therefore cannot be requested + /// SessionOwnerOnly, } @@ -841,19 +889,31 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) /// /// What is returned via after an ownership request has been sent via /// - /// - /// Approved: Granted ownership, and returned after the requesting client has gained ownership on the local instance. - /// Locked: Was locked after request was sent. - /// RequestInProgress: A request started before this request was received. - /// CannotRequest: The RequestRequired status changed while the request was in flight. - /// Denied: General denied message that is only set if returns false by the authority instance. - /// public enum OwnershipRequestResponseStatus { + /// + /// The ownership request was approved and the requesting client has gained ownership on the local instance + /// Approved, + + /// + /// The ownership request was denied because the object became locked after the request was sent + /// Locked, + + /// + /// The ownership request was denied because another request was already in progress when this request was received + /// RequestInProgress, + + /// + /// The ownership request was denied because the RequestRequired status changed while the request was in flight + /// CannotRequest, + + /// + /// The ownership request was denied by the authority instance ( returned false) + /// Denied, } @@ -888,8 +948,19 @@ internal void OwnershipRequestResponse(OwnershipRequestResponseStatus ownershipR /// public enum OwnershipLockActions { + /// + /// No additional locking action will be performed + /// None, + + /// + /// Sets the specified ownership flags and then locks the NetworkObject + /// SetAndLock, + + /// + /// Sets the specified ownership flags and then unlocks the NetworkObject + /// SetAndUnlock } @@ -1115,6 +1186,10 @@ private bool InternalHasAuthority() public bool? IsSceneObject { get; internal set; } //DANGOEXP TODO: Determine if we want to keep this + /// + /// Sets whether this NetworkObject was instantiated as part of a scene + /// + /// When true, marks this as a scene-instantiated object; when false, marks it as runtime-instantiated public void SetSceneObjectStatus(bool isSceneObject = false) { IsSceneObject = isSceneObject; @@ -1186,6 +1261,7 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// Delegate type for checking visibility /// /// The clientId to check visibility for + /// True if the object should be visible to the specified client, false otherwise public delegate bool VisibilityDelegate(ulong clientId); /// @@ -1197,6 +1273,7 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// Delegate type for checking spawn options /// /// The clientId to check spawn options for + /// True if the object should be spawned for the specified client, false otherwise public delegate bool SpawnDelegate(ulong clientId); /// @@ -2676,6 +2753,14 @@ internal bool SetNetworkVariableData(FastBufferReader reader, ulong clientId) return true; } + /// + /// Gets the order index of a NetworkBehaviour instance within the ChildNetworkBehaviours collection + /// + /// The NetworkBehaviour instance to find the index for + /// + /// The index of the NetworkBehaviour in the ChildNetworkBehaviours collection. + /// Returns 0 if the instance is not found. + /// public ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance) { // read the cached index, and verify it first diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index 9a83c6dcc5..815e38d35c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -161,6 +161,8 @@ public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, N /// /// Delegate used to handle named messages /// + /// The client identifier of the message sender + /// The buffer containing the message data to be read public delegate void HandleNamedMessageDelegate(ulong senderClientId, FastBufferReader messagePayload); private Dictionary m_NamedMessageHandlers32 = new Dictionary(); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs index a102f3666e..c8d4cedf45 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs @@ -74,6 +74,10 @@ public class GenerateSerializationForGenericParameterAttribute : Attribute { internal int ParameterIndex; + /// + /// Initializes a new instance of the attribute + /// + /// The zero-based index of the generic parameter that should be serialized public GenerateSerializationForGenericParameterAttribute(int parameterIndex) { ParameterIndex = parameterIndex; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs index 81e55c00df..5bfff33864 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs @@ -18,6 +18,10 @@ public class GenerateSerializationForTypeAttribute : Attribute { internal Type Type; + /// + /// Initializes a new instance of the attribute + /// + /// The type that should have serialization code generated for it public GenerateSerializationForTypeAttribute(Type type) { Type = type; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs index acb6289b5b..6d276ab580 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs @@ -24,12 +24,29 @@ public enum RpcDelivery [AttributeUsage(AttributeTargets.Method)] public class RpcAttribute : Attribute { - // Must match the set of parameters below + /// + /// Parameters that define the behavior of an RPC attribute + /// public struct RpcAttributeParams { + /// + /// Specifies the delivery method for the RPC + /// public RpcDelivery Delivery; + + /// + /// When true, only the owner of the object can execute this RPC + /// public bool RequireOwnership; + + /// + /// When true, local execution of the RPC is deferred until the next network tick + /// public bool DeferLocal; + + /// + /// When true, allows the RPC target to be overridden at runtime + /// public bool AllowTargetOverride; } @@ -38,10 +55,26 @@ public struct RpcAttributeParams /// Type of RPC delivery method /// public RpcDelivery Delivery = RpcDelivery.Reliable; + + /// + /// When true, only the owner of the object can execute this RPC + /// public bool RequireOwnership; + + /// + /// When true, local execution of the RPC is deferred until the next network tick + /// public bool DeferLocal; + + /// + /// When true, allows the RPC target to be overridden at runtime + /// public bool AllowTargetOverride; + /// + /// Initializes a new instance of the RpcAttribute with the specified target + /// + /// The target for this RPC public RpcAttribute(SendTo target) { } @@ -60,8 +93,15 @@ private RpcAttribute() [AttributeUsage(AttributeTargets.Method)] public class ServerRpcAttribute : RpcAttribute { + /// + /// When true, only the owner of the NetworkObject can invoke this ServerRpc. + /// This property overrides the base RpcAttribute.RequireOwnership. + /// public new bool RequireOwnership; + /// + /// Initializes a new instance of ServerRpcAttribute that targets the server + /// public ServerRpcAttribute() : base(SendTo.Server) { @@ -75,6 +115,9 @@ public ServerRpcAttribute() : base(SendTo.Server) [AttributeUsage(AttributeTargets.Method)] public class ClientRpcAttribute : RpcAttribute { + /// + /// Initializes a new instance of ClientRpcAttribute that targets all clients except the server + /// public ClientRpcAttribute() : base(SendTo.NotServer) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs index 7eb1a18009..b302e3f8f7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs @@ -3,22 +3,54 @@ namespace Unity.Netcode { + /// + /// Specifies how RPC messages should be handled in terms of local execution timing + /// public enum LocalDeferMode { + /// + /// Uses the default behavior for RPC message handling + /// Default, + + /// + /// Defers the local execution of the RPC until the next network tick + /// Defer, + + /// + /// Executes the RPC immediately on the local client without waiting for network synchronization + /// SendImmediate } + /// - /// Generic RPC + /// Generic RPC. Defines parameters for sending Remote Procedure Calls (RPCs) in the network system /// public struct RpcSendParams { + /// + /// Specifies the target that will receive this RPC + /// public BaseRpcTarget Target; + /// + /// Controls how the RPC is handled for local execution timing + /// public LocalDeferMode LocalDeferMode; + /// + /// Implicitly converts a BaseRpcTarget to RpcSendParams + /// + /// The RPC target to convert + /// A new RpcSendParams instance with the specified target public static implicit operator RpcSendParams(BaseRpcTarget target) => new RpcSendParams { Target = target }; + + /// + /// Implicitly converts a LocalDeferMode to RpcSendParams + /// + /// The defer mode to convert + /// A new RpcSendParams instance with the specified defer mode public static implicit operator RpcSendParams(LocalDeferMode deferMode) => new RpcSendParams { LocalDeferMode = deferMode }; } @@ -51,9 +83,32 @@ public struct RpcParams /// public RpcReceiveParams Receive; + /// + /// Implicitly converts RpcSendParams to RpcParams + /// + /// The send parameters to convert + /// A new RpcParams instance with the specified send parameters public static implicit operator RpcParams(RpcSendParams send) => new RpcParams { Send = send }; + + /// + /// Implicitly converts a BaseRpcTarget to RpcParams + /// + /// The RPC target to convert + /// A new RpcParams instance with the specified target in its send parameters public static implicit operator RpcParams(BaseRpcTarget target) => new RpcParams { Send = new RpcSendParams { Target = target } }; + + /// + /// Implicitly converts a LocalDeferMode to RpcParams + /// + /// The defer mode to convert + /// A new RpcParams instance with the specified defer mode in its send parameters public static implicit operator RpcParams(LocalDeferMode deferMode) => new RpcParams { Send = new RpcSendParams { LocalDeferMode = deferMode } }; + + /// + /// Implicitly converts RpcReceiveParams to RpcParams + /// + /// The receive parameters to convert + /// A new RpcParams instance with the specified receive parameters public static implicit operator RpcParams(RpcReceiveParams receive) => new RpcParams { Receive = receive }; } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index 94625722e3..5fe5d40818 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -4,10 +4,21 @@ namespace Unity.Netcode { - + /// + /// Defines how anticipated network variables handle authoritative updates that are older than the current anticipated state + /// public enum StaleDataHandling { + /// + /// Ignores authoritative updates that are older than the current anticipated state. + /// The anticipated value will not be replaced until a newer authoritative update is received. + /// Ignore, + + /// + /// Applies authoritative updates even if they are older than the current anticipated state. + /// This triggers reanticipation to calculate a new anticipated value based on the authoritative state. + /// Reanticipate } @@ -83,8 +94,18 @@ public class AnticipatedNetworkVariable : NetworkVariableBase /// the anticipationTime value, and that callback can be used to calculate a new anticipated value. ///
#pragma warning restore IDE0001 + + /// + /// Controls how this network variable handles authoritative updates that are older than the current anticipated state + /// public StaleDataHandling StaleDataHandling; + /// + /// Delegate for handling changes in the authoritative value + /// + /// The network variable that changed + /// The previous value before the change + /// The new value after the change public delegate void OnAuthoritativeValueChangedDelegate(AnticipatedNetworkVariable variable, in T previousValue, in T newValue); /// @@ -121,6 +142,9 @@ public void ResetAnticipation() private AnticipatedObject m_AnticipatedObject; + /// + /// Initializes the network variable, setting up initial values and registering with the anticipation system + /// public override void OnInitialize() { m_AuthoritativeValue.Initialize(m_NetworkBehaviour); @@ -133,6 +157,10 @@ public override void OnInitialize() } } + /// + /// Checks if the current value has changed enough from its last synchronized value to warrant a new network update + /// + /// True if the value should be synchronized, false otherwise public override bool ExceedsDirtinessThreshold() { return m_AuthoritativeValue.ExceedsDirtinessThreshold(); @@ -227,10 +255,19 @@ public T AuthoritativeValue /// See , , , and so on /// for examples. /// + /// The authoritative value to interpolate from + /// The anticipated value to interpolate to + /// The interpolation factor between 0 and 1 + /// The interpolated value public delegate T SmoothDelegate(T authoritativeValue, T anticipatedValue, float amount); private SmoothDelegate m_SmoothDelegate = null; + /// + /// Initializes a new instance of the AnticipatedNetworkVariable class + /// + /// The initial value for the network variable. Defaults to the type's default value if not specified. + /// Determines how the variable handles authoritative updates that are older than the current anticipated state. Defaults to StaleDataHandling.Ignore. public AnticipatedNetworkVariable(T value = default, StaleDataHandling staleDataHandling = StaleDataHandling.Ignore) : base() @@ -242,6 +279,9 @@ public AnticipatedNetworkVariable(T value = default, }; } + /// + /// Updates the smooth interpolation state if active + /// public void Update() { if (m_CurrentSmoothTime < m_SmoothDuration) @@ -253,6 +293,7 @@ public void Update() } } + /// public override void Dispose() { if (m_IsDisposed) @@ -302,6 +343,9 @@ public override void Dispose() } } + /// + /// Finalizer that ensures proper cleanup of network variable resources + /// ~AnticipatedNetworkVariable() { Dispose(); @@ -357,26 +401,31 @@ public void Smooth(in T from, in T to, float durationSeconds, SmoothDelegate how m_HasSmoothValues = true; } + /// public override bool IsDirty() { return m_AuthoritativeValue.IsDirty(); } + /// public override void ResetDirty() { m_AuthoritativeValue.ResetDirty(); } + /// public override void WriteDelta(FastBufferWriter writer) { m_AuthoritativeValue.WriteDelta(writer); } + /// public override void WriteField(FastBufferWriter writer) { m_AuthoritativeValue.WriteField(writer); } + /// public override void ReadField(FastBufferReader reader) { m_AuthoritativeValue.ReadField(reader); @@ -384,6 +433,7 @@ public override void ReadField(FastBufferReader reader) NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); } + /// public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { m_AuthoritativeValue.ReadDelta(reader, keepDirtyDelta); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 72258c77f5..97fbeabc72 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -49,6 +49,9 @@ public NetworkList(IEnumerable values = default, } } + /// + /// Finalizer that ensures proper cleanup of network list resources + /// ~NetworkList() { Dispose(); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index 66fae6a82a..21f92957d2 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -22,10 +22,23 @@ public class NetworkVariable : NetworkVariableBase /// public OnValueChangedDelegate OnValueChanged; + /// + /// Delegate that determines if the difference between two values exceeds a threshold for network synchronization + /// + /// The previous value to compare against + /// The new value to compare + /// True if the difference exceeds the threshold and should be synchronized, false otherwise public delegate bool CheckExceedsDirtinessThresholdDelegate(in T previousValue, in T newValue); + /// + /// Delegate instance for checking if value changes exceed the dirtiness threshold + /// public CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold; + /// + /// Determines if the current value has changed enough from its previous value to warrant network synchronization + /// + /// True if the value should be synchronized, false otherwise public override bool ExceedsDirtinessThreshold() { if (CheckExceedsDirtinessThreshold != null && m_HasPreviousValue) @@ -36,6 +49,9 @@ public override bool ExceedsDirtinessThreshold() return true; } + /// + /// Initializes the NetworkVariable by setting up initial and previous values + /// public override void OnInitialize() { base.OnInitialize(); @@ -140,6 +156,7 @@ public virtual T Value /// If you invoked this when a collection is dirty, it will not trigger the unless you set forceCheck param to true.
/// /// when true, this check will force a full item collection check even if the NetworkVariable is already dirty + /// True if the variable is dirty and needs synchronization, false otherwise public bool CheckDirtyState(bool forceCheck = false) { var isDirty = base.IsDirty(); @@ -178,6 +195,7 @@ internal ref T RefValue() return ref m_InternalValue; } + /// public override void Dispose() { if (m_IsDisposed) @@ -211,6 +229,9 @@ public override void Dispose() base.Dispose(); } + /// + /// Finalizer that ensures proper cleanup of resources + /// ~NetworkVariable() { Dispose(); diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index e8e7221f78..6b97ce90e3 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -3,11 +3,20 @@ namespace Unity.Netcode { + /// + /// Defines update timing constraints for NetworkVariables + /// public struct NetworkVariableUpdateTraits { + /// + /// The minimum amount of time that must pass between sending updates. If this amount of time has not passed since the last update, dirtiness will be ignored. + /// [Tooltip("The minimum amount of time that must pass between sending updates. If this amount of time has not passed since the last update, dirtiness will be ignored.")] public float MinSecondsBetweenUpdates; + /// + /// The maximum amount of time that a variable can be dirty without sending an update. If this amount of time has passed since the last update, an update will be sent even if the dirtiness threshold has not been met. + /// [Tooltip("The maximum amount of time that a variable can be dirty without sending an update. If this amount of time has passed since the last update, an update will be sent even if the dirtiness threshold has not been met.")] public float MaxSecondsBetweenUpdates; } @@ -53,6 +62,10 @@ internal void LogWritePermissionError() private protected NetworkManager m_NetworkManager => m_InternalNetworkManager; + /// + /// Gets the NetworkBehaviour instance associated with this network variable + /// + /// The NetworkBehaviour that owns this network variable public NetworkBehaviour GetBehaviour() { return m_NetworkBehaviour; @@ -98,7 +111,7 @@ public void Initialize(NetworkBehaviour networkBehaviour) if (!m_NetworkBehaviour.NetworkObject.NetworkManagerOwner) { // Exit early if there has yet to be a NetworkManagerOwner assigned - // to the NetworkObject. This is ok because Initialize is invoked + // to the NetworkObject. This is ok because Initialize is invoked // multiple times until it is considered "initialized". return; } @@ -259,6 +272,9 @@ internal void UpdateLastSentTime() internal static bool IgnoreInitializeWarning; + /// + /// Marks the associated NetworkBehaviour as dirty, indicating it needs synchronization + /// protected void MarkNetworkBehaviourDirty() { if (m_NetworkBehaviour == null) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs index b2edd738a3..1a00d2c86a 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -15,8 +15,11 @@ public static class NetworkVariableSerialization internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); /// - /// A callback to check if two values are equal. + /// Delegate for comparing two values of type T for equality /// + /// First value to compare + /// Second value to compare + /// True if the values are equal, false otherwise public delegate bool EqualsDelegate(ref T a, ref T b); /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs index a85d4fcaf6..3f2a64585c 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs @@ -21,6 +21,7 @@ public class UserNetworkVariableSerialization /// /// The to write the value of type `T` /// The value of type `T` to be written + /// The previous value to compute the delta against public delegate void WriteDeltaDelegate(FastBufferWriter writer, in T value, in T previousValue); /// @@ -41,6 +42,7 @@ public class UserNetworkVariableSerialization /// The read value delegate handler definition /// /// The value of type `T` to be read + /// The reference to store the duplicated value in public delegate void DuplicateValueDelegate(in T value, ref T duplicatedValue); /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index e87da1f489..19227c82ba 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1806,6 +1806,9 @@ internal NetworkSpawnManager(NetworkManager networkManager) NetworkManager = networkManager; } + /// + /// Finalizer that ensures proper cleanup of spawn manager resources + /// ~NetworkSpawnManager() { Shutdown(); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs index e39dbc8dfc..b8784fcfb3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs @@ -28,6 +28,10 @@ public abstract class NetworkTransport : MonoBehaviour /// /// Delegate for transport network events /// + /// The type of network event that occurred + /// The ID of the client associated with this event + /// The data payload received with this event + /// The time when this event was received public delegate void TransportEventDelegate(NetworkEvent eventType, ulong clientId, ArraySegment payload, float receiveTime); /// @@ -171,7 +175,7 @@ internal NetworkTopologyTypes CurrentTopology() public enum NetworkTopologyTypes { /// - /// The traditional client-server network topology. + /// The traditional client-server network topology. /// ClientServer, /// diff --git a/pvpExceptions.json b/pvpExceptions.json index 23263680db..c004d0ed32 100644 --- a/pvpExceptions.json +++ b/pvpExceptions.json @@ -7,143 +7,6 @@ }, "PVP-151-1": { "errors": [ - "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnUpdate(): undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkSpawn(): undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkDespawn(): undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnDestroy(): undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnBeforeUpdateTransformState(): undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkTransformStateUpdated(ref NetworkTransformState, ref NetworkTransformState): undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnTransformUpdated(): undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Position: undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Rotation: undocumented", - "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Scale: undocumented", - "Unity.Netcode.NetworkConfig: Prefabs: undocumented", - "Unity.Netcode.NetworkConfig: NetworkTopology: undocumented", - "Unity.Netcode.NetworkConfig: UseCMBService: undocumented", - "Unity.Netcode.NetworkConfig: AutoSpawnPlayerPrefabClientSide: undocumented", - "Unity.Netcode.NetworkPrefab: bool Equals(NetworkPrefab): undocumented", - "Unity.Netcode.NetworkPrefab: SourcePrefabGlobalObjectIdHash: undocumented", - "Unity.Netcode.NetworkPrefab: TargetPrefabGlobalObjectIdHash: undocumented", - "Unity.Netcode.NetworkPrefab: bool Validate(int): undocumented", - "Unity.Netcode.NetworkPrefab: string ToString(): undocumented", - "Unity.Netcode.NetworkPrefabs: Prefabs: undocumented", - "Unity.Netcode.NetworkPrefabs: void Finalize(): undocumented", - "Unity.Netcode.NetworkPrefabs: void Initialize(bool): missing ", - "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", - "Unity.Netcode.NetworkPrefabs: bool Add(NetworkPrefab): missing ", - "Unity.Netcode.NetworkPrefabs: void Remove(NetworkPrefab): missing ", - "Unity.Netcode.NetworkPrefabs: void Remove(GameObject): missing ", - "Unity.Netcode.NetworkClient: NetworkTopologyType: undocumented", - "Unity.Netcode.NetworkClient: DAHost: undocumented", - "Unity.Netcode.ConnectionEventData: EventType: undocumented", - "Unity.Netcode.RpcException: undocumented", - "Unity.Netcode.RpcException: .ctor(string): undocumented", - "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkPostSpawn(): undocumented", - "Unity.Netcode.NetworkBehaviour: void InternalOnNetworkSessionSynchronized(): undocumented", - "Unity.Netcode.NetworkBehaviour: void OnReanticipate(double): undocumented", - "Unity.Netcode.NetworkManager: CurrentSessionOwner: undocumented", - "Unity.Netcode.NetworkManager: void NetworkUpdate(NetworkUpdateStage): undocumented", - "Unity.Netcode.NetworkManager.ReanticipateDelegate: undocumented", - "Unity.Netcode.NetworkObject: void SetSceneObjectStatus(bool): undocumented", - "Unity.Netcode.NetworkObject: ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour): undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: None: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: Distributable: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: Transferable: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: RequestRequired: undocumented", - "Unity.Netcode.NetworkObject.OwnershipStatus: SessionOwner: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: Locked: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestRequired: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: RequestInProgress: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: NotTransferrable: undocumented", - "Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus: SessionOwnerOnly: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestSent: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: AlreadyOwner: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestRequiredNotSet: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: Locked: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: RequestInProgress: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestStatus: SessionOwnerOnly: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Approved: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Locked: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: RequestInProgress: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: CannotRequest: undocumented", - "Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus: Denied: undocumented", - "Unity.Netcode.NetworkObject.OwnershipLockActions: None: undocumented", - "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndLock: undocumented", - "Unity.Netcode.NetworkObject.OwnershipLockActions: SetAndUnlock: undocumented", - "Unity.Netcode.NetworkObject.VisibilityDelegate: missing ", - "Unity.Netcode.NetworkObject.SpawnDelegate: missing ", - "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", - "Unity.Netcode.CustomMessagingManager.HandleNamedMessageDelegate: missing ", - "Unity.Netcode.GenerateSerializationForGenericParameterAttribute: .ctor(int): undocumented", - "Unity.Netcode.GenerateSerializationForTypeAttribute: .ctor(Type): undocumented", - "Unity.Netcode.RpcAttribute: RequireOwnership: undocumented", - "Unity.Netcode.RpcAttribute: DeferLocal: undocumented", - "Unity.Netcode.RpcAttribute: AllowTargetOverride: undocumented", - "Unity.Netcode.RpcAttribute: .ctor(SendTo): undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: Delivery: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: RequireOwnership: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: DeferLocal: undocumented", - "Unity.Netcode.RpcAttribute.RpcAttributeParams: AllowTargetOverride: undocumented", - "Unity.Netcode.ServerRpcAttribute: RequireOwnership: undocumented", - "Unity.Netcode.ServerRpcAttribute: .ctor(): undocumented", - "Unity.Netcode.ClientRpcAttribute: .ctor(): undocumented", - "Unity.Netcode.LocalDeferMode: undocumented", - "Unity.Netcode.LocalDeferMode: Default: undocumented", - "Unity.Netcode.LocalDeferMode: Defer: undocumented", - "Unity.Netcode.LocalDeferMode: SendImmediate: undocumented", - "Unity.Netcode.RpcSendParams: Target: undocumented", - "Unity.Netcode.RpcSendParams: LocalDeferMode: undocumented", - "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(BaseRpcTarget): undocumented", - "Unity.Netcode.RpcSendParams: RpcSendParams op_Implicit(LocalDeferMode): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcSendParams): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(BaseRpcTarget): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(LocalDeferMode): undocumented", - "Unity.Netcode.RpcParams: RpcParams op_Implicit(RpcReceiveParams): undocumented", - "Unity.Netcode.StaleDataHandling: undocumented", - "Unity.Netcode.StaleDataHandling: Ignore: undocumented", - "Unity.Netcode.StaleDataHandling: Reanticipate: undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void OnInitialize(): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: bool ExceedsDirtinessThreshold(): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: .ctor(T, StaleDataHandling): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void Update(): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void Dispose(): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void Finalize(): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: bool IsDirty(): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void ResetDirty(): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void WriteDelta(FastBufferWriter): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void WriteField(FastBufferWriter): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void ReadField(FastBufferReader): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable: void ReadDelta(FastBufferReader, bool): undocumented", - "Unity.Netcode.AnticipatedNetworkVariable.OnAuthoritativeValueChangedDelegate: undocumented", - "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", - "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", - "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", - "Unity.Netcode.AnticipatedNetworkVariable.SmoothDelegate: missing ", - "Unity.Netcode.NetworkList: void Finalize(): undocumented", - "Unity.Netcode.NetworkVariable: CheckExceedsDirtinessThreshold: undocumented", - "Unity.Netcode.NetworkVariable: bool ExceedsDirtinessThreshold(): undocumented", - "Unity.Netcode.NetworkVariable: void OnInitialize(): undocumented", - "Unity.Netcode.NetworkVariable: bool CheckDirtyState(bool): missing ", - "Unity.Netcode.NetworkVariable: void Dispose(): undocumented", - "Unity.Netcode.NetworkVariable: void Finalize(): undocumented", - "Unity.Netcode.NetworkVariable.CheckExceedsDirtinessThresholdDelegate: undocumented", - "Unity.Netcode.NetworkVariableUpdateTraits: undocumented", - "Unity.Netcode.NetworkVariableUpdateTraits: MinSecondsBetweenUpdates: undocumented", - "Unity.Netcode.NetworkVariableUpdateTraits: MaxSecondsBetweenUpdates: undocumented", - "Unity.Netcode.NetworkVariableBase: NetworkBehaviour GetBehaviour(): undocumented", - "Unity.Netcode.NetworkVariableBase: void MarkNetworkBehaviourDirty(): undocumented", - "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", - "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", - "Unity.Netcode.NetworkVariableSerialization.EqualsDelegate: missing ", - "Unity.Netcode.UserNetworkVariableSerialization.WriteDeltaDelegate: missing ", - "Unity.Netcode.UserNetworkVariableSerialization.DuplicateValueDelegate: missing ", - "Unity.Netcode.NetworkSpawnManager: void Finalize(): undocumented", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", - "Unity.Netcode.NetworkTransport.TransportEventDelegate: missing ", "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: undocumented", "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void OnNetworkSpawn(): undocumented", "Unity.Netcode.TestHelpers.Runtime.ObjectNameIdentifier: void RegisterAndLabelNetworkObject(): undocumented", From 57c8cdd69709acef90e8449f789bd23f458b1a50 Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 09:37:47 +0200 Subject: [PATCH 227/236] chore(deps): update actions/checkout action to v4 (develop-2.0.0) (#3397) This update our internally used actions/checkout version --- .github/workflows/backport-verification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport-verification.yml b/.github/workflows/backport-verification.yml index 0ebe899391..7e9546d7ba 100644 --- a/.github/workflows/backport-verification.yml +++ b/.github/workflows/backport-verification.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check PR description uses: actions/github-script@v6 From 1402294d875953c8ece7567020c6c40730fa8dfa Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 09:40:09 +0200 Subject: [PATCH 228/236] chore(deps): update actions/github-script action to v7 (develop-2.0.0) (#3398) This PR updates our internally used actions/github-script --- .github/workflows/backport-verification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport-verification.yml b/.github/workflows/backport-verification.yml index 7e9546d7ba..4e82b13af6 100644 --- a/.github/workflows/backport-verification.yml +++ b/.github/workflows/backport-verification.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v4 - name: Check PR description - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const pr = context.payload.pull_request; From e45e3b9716294a2a3272e90a347a3f2d59722ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:07:42 +0200 Subject: [PATCH 229/236] ci: Increased WebGL job timeout (#3399) This PR follows up on https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/3389 and increases the timeout even more because it was noted that the issue is still happening. After some testing it seems that this timeout is sufficient but some investigation should be performed in the future to see why the job times out more often in such configuration (develop-2.0.0/ubuntu/trunk) ## Backport Since this PR is targeting an Issue that happens mostly in develop-2.0.0, ubuntu/trunk configuration no backport is needed (there is no that big of a problem in develop branch) --- .yamato/webgl-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.yamato/webgl-build.yml b/.yamato/webgl-build.yml index 3729ffb965..881c433cbe 100644 --- a/.yamato/webgl-build.yml +++ b/.yamato/webgl-build.yml @@ -20,6 +20,7 @@ # QUALITY CONSIDERATIONS-------------------------------------------------------------------- # In the future we could try to implement an infrastructure to run test in webgl context but this could be quite complicated and would need to be evaluated if it's worth it # To see where this job is included (in trigger job definitions) look into _triggers.yml file + # WebGL jobs were timing up more often in develop-2.0.0 branch (especially on trunk) so we increased the timeout from 1800 to 3600. We should investigate why it times more often in develop-2.0.0/ubuntu/trunk #-------------------------------------------------------------------------------------- @@ -45,7 +46,7 @@ webgl_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: # Engine is initialized in ‘nographics’ mode since we don't need any graphics for this case (--extra-editor-arg=-nographics) # In case of failure the job will be rerunned once (--reruncount=1) with clean library (--clean-library-on-rerun) # This will perform only building phase (--build-only) with a timeout of 3m (--timeout=1800) - - UnifiedTestRunner --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts_path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=2400 + - UnifiedTestRunner --suite=playmode --platform=WebGL --scripting-backend=il2cpp --testproject={{ project.path }} --editor-location=.Editor --artifacts_path=artifacts --player-save-path=build/players --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout=3600 artifacts: logs: paths: From f6a6c1f3efb44e393759b37feec2498193a5df8d Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 14 Apr 2025 04:10:24 -0500 Subject: [PATCH 230/236] chore: merge 2.3.0 back to develop-2.0.0 (#3390) This PR merges the changes made to the v2.3.0 branch back into develop-2.0.0 while also adding an unreleased section in the changelog. ## Changelog NA ## Testing and Documentation - No tests have been added. - No documentation changes or additions were necessary. --- com.unity.netcode.gameobjects/CHANGELOG.md | 12 ++++++++++++ .../Interpolator/BufferedLinearInterpolator.cs | 15 ++++++++++++++- .../Tests/Runtime/NetworkVisibilityTests.cs | 5 +++-- com.unity.netcode.gameobjects/package.json | 2 +- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c4edcc652c..93ed4d1732 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -8,6 +8,18 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [Unreleased] +### Added + + +### Fixed + + +### Changed + + + +## [2.3.0] - 2025-04-09 + ### Added - Added `NetworkManager.OnPreShutdown` which is called before the NetworkManager cleans up and shuts down. (#3366) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index b07eca8250..f96bb55392 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -101,7 +101,7 @@ protected internal struct BufferedItem public double TimeSent; /// - /// The constructor. + /// Constructor that accepts an item identifier. /// /// The item value. /// The time the item was sent. @@ -112,6 +112,19 @@ public BufferedItem(T item, double timeSent, int itemId) TimeSent = timeSent; ItemId = itemId; } + + /// + /// The original constructor. + /// + /// The item value. + /// The time the item was sent. + public BufferedItem(T item, double timeSent) + { + Item = item; + TimeSent = timeSent; + // Generate a unique item id based on the time to the 2nd decimal place + ItemId = (int)(timeSent * 100); + } } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs index a19f856ad1..670bd9d091 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVisibilityTests.cs @@ -87,8 +87,9 @@ public IEnumerator HideShowAndDeleteTest() sessionOwnerNetworkObject.NetworkShow(m_ClientNetworkManagers[clientIndex].LocalClientId); sessionOwnerNetworkObject.Despawn(true); - // Expect no exceptions - yield return s_DefaultWaitForTick; + // Expect no exceptions while waiting to show the object and wait for the client id to be removed + yield return WaitForConditionOrTimeOut(() => !m_SessionOwner.SpawnManager.ObjectsToShowToClient.ContainsKey(m_ClientNetworkManagers[clientIndex].LocalClientId)); + AssertOnTimeout($"Timed out waiting for client-{m_ClientNetworkManagers[clientIndex].LocalClientId} to be removed from the {nameof(NetworkSpawnManager.ObjectsToShowToClient)} table!"); // Now force a scenario where it normally would have caused an exception m_SessionOwner.SpawnManager.ObjectsToShowToClient.Add(m_ClientNetworkManagers[clientIndex].LocalClientId, new System.Collections.Generic.List()); diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index c20e366c3c..b8b6581016 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.2.0", + "version": "2.3.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From f2e776e2ddfd86b2e31d5479b0c5c6ab05d6fe97 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 14 Apr 2025 12:42:50 -0500 Subject: [PATCH 231/236] fix: instantiate, spawn, and parent when spawning (#3401) This PR resolves the issue where instantiating and spawning a `NetworkObject` (A) during another `NetworkObject`'s spawn process (B) and then parenting the newly spawned `NetworkObject` (A) under the `NetworkObject` (B) would end up causing the parenting message to not properly defer the parenting message until the parent (B) was fully spawned on other already connected and synchronized clients (it would properly parent if another client late joined shortly after). [MTTB-1209](https://jira.unity3d.com/browse/MTTB-1209) fix: #3100 ## Changelog - Fixed: Issue where during a `NetworkObject` spawn if you instantiated, spawned, and parented another network prefab under the currently spawning `NetworkObject` the parenting message would not properly defer until the parent `NetworkObject` was spawned. ## Testing and Documentation - Includes new integration ParentingDuringSpawnTests test. - No documentation changes or additions were necessary. ## Backport This requires a backport to v1.x as the `ParentSyncMessage` does not defer the message if the parent `NetworkObject` is not spawned yet. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Messaging/Messages/ParentSyncMessage.cs | 14 +- .../Runtime/ParentingDuringSpawnTests.cs | 165 ++++++++++++++++++ .../Runtime/ParentingDuringSpawnTests.cs.meta | 2 + 4 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 93ed4d1732..695e000a45 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where during a `NetworkObject`'s spawn if you instantiated, spawned, and parented another network prefab under the currently spawning `NetworkObject` the parenting message would not properly defer until the parent `NetworkObject` was spawned. (#3401) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs index 33caa7d114..c2965d4b31 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -6,7 +6,7 @@ internal struct ParentSyncMessage : INetworkMessage { public int Version => 0; - private const string k_Name = "DestroyObjectMessage"; + private const string k_Name = "ParentSyncMessage"; public ulong NetworkObjectId; @@ -87,12 +87,20 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int reader.ReadValueSafe(out Rotation); reader.ReadValueSafe(out Scale); - // If the target NetworkObject does not exist =or= the target latest parent does not exist then defer the message - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId) || (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value))) + // If the target NetworkObject does not exist then defer this message until it does. + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) { networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name); return false; } + + // If the target parent does not exist, then defer this message until it does. + if (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value)) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, LatestParent.Value, reader, ref context, k_Name); + return false; + } + return true; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs new file mode 100644 index 0000000000..9623c997a8 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs @@ -0,0 +1,165 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(NetworkTopologyTypes.ClientServer, NetworkSpawnTypes.OnNetworkSpawn)] + [TestFixture(NetworkTopologyTypes.ClientServer, NetworkSpawnTypes.OnNetworkPostSpawn)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, NetworkSpawnTypes.OnNetworkSpawn)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, NetworkSpawnTypes.OnNetworkPostSpawn)] + internal class ParentingDuringSpawnTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 2; + + public enum NetworkSpawnTypes + { + OnNetworkSpawn, + OnNetworkPostSpawn, + } + + private NetworkSpawnTypes m_NetworkSpawnType; + + private GameObject m_ParentPrefab; + private GameObject m_ChildPrefab; + private NetworkObject m_AuthorityInstance; + private List m_NetworkManagers = new List(); + private StringBuilder m_Errors = new StringBuilder(); + + public class ParentDuringSpawnBehaviour : NetworkBehaviour + { + public GameObject ChildToSpawn; + + public NetworkSpawnTypes NetworkSpawnType; + + public Transform ChildSpawnPoint; + + private void SpawnThenParent() + { + var child = NetworkObject.InstantiateAndSpawn(ChildToSpawn, NetworkManager, position: ChildSpawnPoint.position, rotation: ChildSpawnPoint.rotation); + if (!child.TrySetParent(NetworkObject)) + { + var errorMessage = $"[{ChildToSpawn}] Failed to parent child {child.name} under parent {gameObject.name}!"; + Debug.LogError(errorMessage); + } + } + + public override void OnNetworkSpawn() + { + if (HasAuthority && NetworkSpawnType == NetworkSpawnTypes.OnNetworkSpawn) + { + SpawnThenParent(); + } + + base.OnNetworkSpawn(); + } + + protected override void OnNetworkPostSpawn() + { + if (HasAuthority && NetworkSpawnType == NetworkSpawnTypes.OnNetworkPostSpawn) + { + SpawnThenParent(); + } + base.OnNetworkPostSpawn(); + } + } + + public ParentingDuringSpawnTests(NetworkTopologyTypes networkTopology, NetworkSpawnTypes networkSpawnType) : base(networkTopology) + { + m_NetworkSpawnType = networkSpawnType; + } + + protected override void OnServerAndClientsCreated() + { + m_ParentPrefab = CreateNetworkObjectPrefab("Parent"); + m_ChildPrefab = CreateNetworkObjectPrefab("Child"); + var parentComponet = m_ParentPrefab.AddComponent(); + parentComponet.ChildToSpawn = m_ChildPrefab; + var spawnPoint = new GameObject(); + parentComponet.ChildSpawnPoint = spawnPoint.transform; + parentComponet.ChildSpawnPoint.position = GetRandomVector3(-5.0f, 5.0f); + var rotation = parentComponet.ChildSpawnPoint.rotation; + rotation.eulerAngles = GetRandomVector3(-180.0f, 180.0f); + parentComponet.ChildSpawnPoint.rotation = rotation; + base.OnServerAndClientsCreated(); + } + + private bool NonAuthorityInstancesSpawnedParent() + { + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityInstance.NetworkObjectId)) + { + return false; + } + } + return true; + } + + private bool NonAuthorityInstancesParentedChild() + { + m_Errors.Clear(); + if (m_AuthorityInstance.transform.childCount == 0) + { + return false; + } + var authorityChildObject = m_AuthorityInstance.transform.GetChild(0).GetComponent(); + + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(authorityChildObject.NetworkObjectId)) + { + m_Errors.AppendLine($"{networkManager.name} has not spawned the child {authorityChildObject.name}!"); + return false; + } + var childObject = networkManager.SpawnManager.SpawnedObjects[authorityChildObject.NetworkObjectId]; + + if (childObject.transform.parent == null) + { + m_Errors.AppendLine($"{childObject.name} does not have a parent!"); + return false; + } + + if (!Approximately(authorityChildObject.transform.position, childObject.transform.position)) + { + m_Errors.AppendLine($"{childObject.name} position {GetVector3Values(childObject.transform.position)} does " + + $"not match the authority's position {GetVector3Values(authorityChildObject.transform.position)}!"); + return false; + } + + if (!Approximately(authorityChildObject.transform.rotation, childObject.transform.rotation)) + { + m_Errors.AppendLine($"{childObject.name} rotation {GetVector3Values(childObject.transform.rotation.eulerAngles)} does " + + $"not match the authority's position {GetVector3Values(authorityChildObject.transform.rotation.eulerAngles)}!"); + return false; + } + } + return true; + } + + [UnityTest] + public IEnumerator ParentDuringSpawn() + { + m_NetworkManagers.Clear(); + var authorityNetworkManager = m_DistributedAuthority ? m_ClientNetworkManagers[0] : m_ServerNetworkManager; + + m_NetworkManagers.AddRange(m_ClientNetworkManagers); + if (!UseCMBService()) + { + m_NetworkManagers.Add(m_ServerNetworkManager); + } + + m_AuthorityInstance = SpawnObject(m_ParentPrefab, authorityNetworkManager).GetComponent(); + + yield return WaitForConditionOrTimeOut(NonAuthorityInstancesSpawnedParent); + AssertOnTimeout($"Not all clients spawned the parent {nameof(NetworkObject)}!"); + + yield return WaitForConditionOrTimeOut(NonAuthorityInstancesParentedChild); + AssertOnTimeout($"Non-Authority instance had a mismatched value: \n {m_Errors}"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs.meta new file mode 100644 index 0000000000..abf773ea1c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1d708e3d4d5f1794aa0a924eaa9445d6 \ No newline at end of file From 5267b2b48e29198ce3a7e25301541e1cdb0e6580 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 14 Apr 2025 12:46:22 -0500 Subject: [PATCH 232/236] update Setting the v2.3.1 change log --- com.unity.netcode.gameobjects/CHANGELOG.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 695e000a45..982f6bac83 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -6,18 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [Unreleased] - -### Added - +## [2.3.1] - 2025-04-14 ### Fixed - Fixed issue where during a `NetworkObject`'s spawn if you instantiated, spawned, and parented another network prefab under the currently spawning `NetworkObject` the parenting message would not properly defer until the parent `NetworkObject` was spawned. (#3401) -### Changed - - ## [2.3.0] - 2025-04-09 From bc4653727fe286e09176aec715e1f52d17e24685 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 14 Apr 2025 12:46:37 -0500 Subject: [PATCH 233/236] update Setting the package version --- com.unity.netcode.gameobjects/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index b8b6581016..13044317ac 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.3.0", + "version": "2.3.1", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 73c3a1835d1967a5d8fe498a83ba2bbb9967e527 Mon Sep 17 00:00:00 2001 From: Ken Roecks Date: Sat, 19 Apr 2025 13:37:21 -0700 Subject: [PATCH 234/236] Merge updates --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 2 +- .../Runtime/SceneManagement/NetworkSceneManager.cs | 4 ++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index f0e388d72e..4838aae9e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1090,7 +1090,7 @@ public int MaximumFragmentedMessageSize get => MessageManager.FragmentedMessageMaxSize; } - internal void Initialize(bool server) + public virtual void Initialize(bool server) { #if DEVELOPMENT_BUILD || UNITY_EDITOR if (!DisableNotOptimizedSerializedType) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index b0f0722671..7f8f56f0db 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -3348,7 +3348,7 @@ public List GetSceneMapping(MapTypes mapType) { foreach (var entry in ServerSceneHandleToClientSceneHandle) { - var scene = ScenesLoaded[entry.Value]; + var scene = ScenesLoaded[entry.Value].SceneReference; var sceneIsPresent = scene.IsValid() && scene.isLoaded; var sceneMap = new SceneMap() { @@ -3367,7 +3367,7 @@ public List GetSceneMapping(MapTypes mapType) { foreach (var entry in ClientSceneHandleToServerSceneHandle) { - var scene = ScenesLoaded[entry.Key]; + var scene = ScenesLoaded[entry.Key].SceneReference; var sceneIsPresent = scene.IsValid() && scene.isLoaded; var sceneMap = new SceneMap() { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 19227c82ba..64af92ed8f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1517,7 +1517,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep() var clearFirst = true; foreach (var sceneLoaded in NetworkManager.SceneManager.ScenesLoaded) { - NetworkManager.SceneManager.PopulateScenePlacedObjects(sceneLoaded.Value, clearFirst); + NetworkManager.SceneManager.PopulateScenePlacedObjects(sceneLoaded.Value.SceneReference, clearFirst); clearFirst = false; } From 00744cd64fe76d814a37cf0be6b8d571b76e7759 Mon Sep 17 00:00:00 2001 From: Ken Roecks Date: Sat, 19 Apr 2025 14:53:18 -0700 Subject: [PATCH 235/236] logging --- .../Runtime/SceneManagement/NetworkSceneManager.cs | 8 +++++++- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 7f8f56f0db..084fd6286e 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1875,6 +1875,8 @@ private void OnSceneLoaded(uint sceneEventId, string loadedSceneName) } } + Log.Info(() => "OnSceneLoaded"); + //Get all NetworkObjects loaded by the scene PopulateScenePlacedObjects(nextScene); @@ -2300,6 +2302,8 @@ private void ClientLoadedSynchronization(uint sceneEventId, string sceneName) throw new Exception($"Server Scene Handle ({sceneEventData.SceneHandle}) already exist! Happened during scene load of {nextScene.name} with Client Handle ({nextScene.handle})"); } + Log.Info(() => $"ClientLoadedSynchronization sceneName={sceneName}"); + // Apply all in-scene placed NetworkObjects loaded by the scene PopulateScenePlacedObjects(nextScene, false); @@ -2427,6 +2431,8 @@ private void HandleClientSceneEvent(uint sceneEventId) } else { + Log.Info(() => $"SceneEventType.Synchronize sceneName={sceneEventData.ClientSceneName}"); + // Include anything in the DDOL scene PopulateScenePlacedObjects(DontDestroyOnLoadScene, false); @@ -2930,7 +2936,7 @@ internal void MoveObjectsToDontDestroyOnLoad() /// internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true) { - Log.Info(() => "PopulateScenePlacedObjects"); + Log.Info(() => "PopulateScenePlacedObjects"); if (clearScenePlacedObjects) { ScenePlacedObjects.Clear(); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 64af92ed8f..363cd4f0aa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1512,6 +1512,8 @@ internal void ServerSpawnSceneObjectsOnStartSweep() } } + Log.Info(() => "ServerSpawnSceneObjectsOnStartSweep"); + // Since we are spawing in-scene placed NetworkObjects for already loaded scenes, // we need to add any in-scene placed NetworkObject to our tracking table var clearFirst = true; From 07b9508d6158693d86f10f01d38154e45dff01c9 Mon Sep 17 00:00:00 2001 From: Ken Roecks Date: Sat, 19 Apr 2025 14:58:56 -0700 Subject: [PATCH 236/236] add log --- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 363cd4f0aa..86a116ee0d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using UnityEngine; +using TrollKing.Core; namespace Unity.Netcode { @@ -12,6 +13,7 @@ namespace Unity.Netcode /// public class NetworkSpawnManager { + private static readonly NetworkLogScope Log = new NetworkLogScope(nameof(NetworkSpawnManager)); // Stores the objects that need to be shown at end-of-frame internal Dictionary> ObjectsToShowToClient = new Dictionary>();